diff --git a/.coveragerc b/.coveragerc
index a335557d4f1..b810471417f 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -29,3 +29,5 @@ exclude_lines =
 
     ^\s*if TYPE_CHECKING:
     ^\s*@overload( |$)
+
+    ^\s*@pytest\.mark\.xfail
diff --git a/.gitblameignore b/.git-blame-ignore-revs
similarity index 66%
rename from .gitblameignore
rename to .git-blame-ignore-revs
index 0cb298b024d..4f4cdb6c564 100644
--- a/.gitblameignore
+++ b/.git-blame-ignore-revs
@@ -5,7 +5,7 @@
 #
 # To "install" it:
 #
-#   git config --local blame.ignoreRevsFile .gitblameignore
+#   git config --local blame.ignoreRevsFile .git-blame-ignore-revs
 
 # run black
 703e4b11ba76171eccd3f13e723c47b810ded7ef
@@ -23,6 +23,13 @@ afc607cfd81458d4e4f3b1f3cf8cc931b933907e
 5f95dce95602921a70bfbc7d8de2f7712c5e4505
 # ran pyupgrade-docs again
 75d0b899bbb56d6849e9d69d83a9426ed3f43f8b
-
 # move argument parser to own file
 c9df77cbd6a365dcb73c39618e4842711817e871
+# Replace reorder-python-imports by isort due to black incompatibility (#11896)
+8b54596639f41dfac070030ef20394b9001fe63c
+# Run blacken-docs with black's 2024's style
+4546d5445aaefe6a03957db028c263521dfb5c4b
+# Migration to ruff / ruff format
+4588653b2497ed25976b7aaff225b889fb476756
+# Use format specifiers instead of percent format
+4788165e69d08e10fc6b9c0124083fb358e2e9b0
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 5f2d1cf09c8..88049407b45 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,5 +1,7 @@
 # info:
 # * https://help.github.com/en/articles/displaying-a-sponsor-button-in-your-repository
 # * https://tidelift.com/subscription/how-to-connect-tidelift-with-github
+github: pytest-dev
 tidelift: pypi/pytest
 open_collective: pytest
+thanks_dev: u/gh/pytest-dev
diff --git a/.github/chronographer.yml b/.github/chronographer.yml
new file mode 100644
index 00000000000..803db1e3417
--- /dev/null
+++ b/.github/chronographer.yml
@@ -0,0 +1,20 @@
+---
+
+branch-protection-check-name: Changelog entry
+action-hints:
+  check-title-prefix: "Chronographer: "
+  external-docs-url: >-
+    https://docs.pytest.org/en/latest/contributing.html#preparing-pull-requests
+  inline-markdown: >-
+    See
+    https://docs.pytest.org/en/latest/contributing.html#preparing-pull-requests
+    for details.
+enforce-name:
+  suffix: .rst
+exclude:
+  humans:
+  - pyup-bot
+labels:
+  skip-changelog: skip news
+
+...
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 507789bf5a4..294b13743e2 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -9,3 +9,9 @@ updates:
   allow:
   - dependency-type: direct
   - dependency-type: indirect
+- package-ecosystem: github-actions
+  directory: /
+  schedule:
+    interval: weekly
+    time: "03:00"
+  open-pull-requests-limit: 10
diff --git a/.github/patchback.yml b/.github/patchback.yml
new file mode 100644
index 00000000000..5d62fca12fe
--- /dev/null
+++ b/.github/patchback.yml
@@ -0,0 +1,7 @@
+---
+
+backport_branch_prefix: patchback/backports/
+backport_label_prefix: 'backport '  # IMPORTANT: the labels are space-delimited
+# target_branch_prefix: ''  # The project's backport branches are non-prefixed
+
+...
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 00000000000..2ad6e9d5054
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,116 @@
+name: deploy
+
+on:
+  workflow_dispatch:
+    inputs:
+      version:
+        description: 'Release version'
+        required: true
+        default: '1.2.3'
+
+
+# Set permissions at the job level.
+permissions: {}
+
+jobs:
+  package:
+    runs-on: ubuntu-latest
+    env:
+      SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }}
+    timeout-minutes: 10
+
+    # Required by attest-build-provenance-github.
+    permissions:
+      id-token: write
+      attestations: write
+
+    steps:
+    - uses: actions/checkout@v4
+      with:
+        fetch-depth: 0
+        persist-credentials: false
+
+    - name: Build and Check Package
+      uses: hynek/build-and-inspect-python-package@v2.12.0
+      with:
+        attest-build-provenance-github: 'true'
+
+  deploy:
+    if: github.repository == 'pytest-dev/pytest'
+    needs: [package]
+    runs-on: ubuntu-latest
+    environment: deploy
+    timeout-minutes: 30
+    permissions:
+      id-token: write
+      contents: write
+    steps:
+    - uses: actions/checkout@v4
+      with:
+        persist-credentials: true
+
+    - name: Download Package
+      uses: actions/download-artifact@v4
+      with:
+        name: Packages
+        path: dist
+
+    - name: Publish package to PyPI
+      uses: pypa/gh-action-pypi-publish@v1.12.4
+      with:
+        attestations: true
+
+    - name: Push tag
+      env:
+          VERSION: ${{ github.event.inputs.version }}
+      run: |
+        git config user.name "pytest bot"
+        git config user.email "pytestbot@gmail.com"
+        git tag --annotate --message=v"$VERSION" "$VERSION" ${{ github.sha }}
+        git push origin "$VERSION"
+
+  release-notes:
+
+    # todo: generate the content in the build  job
+    #       the goal being of using a github action script to push the release data
+    #       after success instead of creating a complete python/tox env
+    needs: [deploy]
+    runs-on: ubuntu-latest
+    timeout-minutes: 30
+    permissions:
+      contents: write
+    steps:
+    - uses: actions/checkout@v4
+      with:
+        fetch-depth: 0
+        persist-credentials: false
+
+    - name: Download Package
+      uses: actions/download-artifact@v4
+      with:
+        name: Packages
+        path: dist
+
+    - name: Set up Python
+      uses: actions/setup-python@v5
+      with:
+        python-version: "3.11"
+
+    - name: Install tox
+      run: |
+        python -m pip install --upgrade pip
+        pip install --upgrade tox
+
+    - name: Generate release notes
+      env:
+        VERSION: ${{ github.event.inputs.version }}
+      run: |
+        sudo apt-get install pandoc
+        tox -e generate-gh-release-notes -- "$VERSION" scripts/latest-release-notes.md
+
+    - name: Publish GitHub Release
+      uses: softprops/action-gh-release@v2
+      with:
+        body_path: scripts/latest-release-notes.md
+        files: dist/*
+        tag_name: ${{ github.event.inputs.version }}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index 92e2dc6be7c..00000000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,221 +0,0 @@
-name: main
-
-on:
-  push:
-    branches:
-      - main
-      - "[0-9]+.[0-9]+.x"
-    tags:
-      - "[0-9]+.[0-9]+.[0-9]+"
-      - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
-
-  pull_request:
-    branches:
-      - main
-      - "[0-9]+.[0-9]+.x"
-
-env:
-  PYTEST_ADDOPTS: "--color=yes"
-
-# Set permissions at the job level.
-permissions: {}
-
-jobs:
-  build:
-    runs-on: ${{ matrix.os }}
-    timeout-minutes: 30
-    permissions:
-      contents: read
-
-    strategy:
-      fail-fast: false
-      matrix:
-        name: [
-          "windows-py36",
-          "windows-py37",
-          "windows-py37-pluggy",
-          "windows-py38",
-          "windows-py39",
-          "windows-py310",
-
-          "ubuntu-py36",
-          "ubuntu-py37",
-          "ubuntu-py37-pluggy",
-          "ubuntu-py37-freeze",
-          "ubuntu-py38",
-          "ubuntu-py39",
-          "ubuntu-py310",
-          "ubuntu-pypy3",
-
-          "macos-py37",
-          "macos-py38",
-
-          "docs",
-          "doctesting",
-          "plugins",
-        ]
-
-        include:
-          - name: "windows-py36"
-            python: "3.6"
-            os: windows-latest
-            tox_env: "py36-xdist"
-          - name: "windows-py37"
-            python: "3.7"
-            os: windows-latest
-            tox_env: "py37-numpy"
-          - name: "windows-py37-pluggy"
-            python: "3.7"
-            os: windows-latest
-            tox_env: "py37-pluggymain-xdist"
-          - name: "windows-py38"
-            python: "3.8"
-            os: windows-latest
-            tox_env: "py38-unittestextras"
-            use_coverage: true
-          - name: "windows-py39"
-            python: "3.9"
-            os: windows-latest
-            tox_env: "py39-xdist"
-          - name: "windows-py310"
-            python: "3.10-dev"
-            os: windows-latest
-            tox_env: "py310-xdist"
-
-          - name: "ubuntu-py36"
-            python: "3.6"
-            os: ubuntu-latest
-            tox_env: "py36-xdist"
-          - name: "ubuntu-py37"
-            python: "3.7"
-            os: ubuntu-latest
-            tox_env: "py37-lsof-numpy-pexpect"
-            use_coverage: true
-          - name: "ubuntu-py37-pluggy"
-            python: "3.7"
-            os: ubuntu-latest
-            tox_env: "py37-pluggymain-xdist"
-          - name: "ubuntu-py37-freeze"
-            python: "3.7"
-            os: ubuntu-latest
-            tox_env: "py37-freeze"
-          - name: "ubuntu-py38"
-            python: "3.8"
-            os: ubuntu-latest
-            tox_env: "py38-xdist"
-          - name: "ubuntu-py39"
-            python: "3.9"
-            os: ubuntu-latest
-            tox_env: "py39-xdist"
-          - name: "ubuntu-py310"
-            python: "3.10-dev"
-            os: ubuntu-latest
-            tox_env: "py310-xdist"
-          - name: "ubuntu-pypy3"
-            python: "pypy-3.7"
-            os: ubuntu-latest
-            tox_env: "pypy3-xdist"
-
-          - name: "macos-py37"
-            python: "3.7"
-            os: macos-latest
-            tox_env: "py37-xdist"
-          - name: "macos-py38"
-            python: "3.8"
-            os: macos-latest
-            tox_env: "py38-xdist"
-            use_coverage: true
-
-          - name: "plugins"
-            python: "3.7"
-            os: ubuntu-latest
-            tox_env: "plugins"
-
-          - name: "docs"
-            python: "3.7"
-            os: ubuntu-latest
-            tox_env: "docs"
-          - name: "doctesting"
-            python: "3.7"
-            os: ubuntu-latest
-            tox_env: "doctesting"
-            use_coverage: true
-
-    steps:
-    - uses: actions/checkout@v2
-      with:
-        fetch-depth: 0
-        persist-credentials: false
-
-    - name: Set up Python ${{ matrix.python }}
-      uses: actions/setup-python@v2
-      with:
-        python-version: ${{ matrix.python }}
-
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        pip install tox coverage
-
-    - name: Test without coverage
-      if: "! matrix.use_coverage"
-      run: "tox -e ${{ matrix.tox_env }}"
-
-    - name: Test with coverage
-      if: "matrix.use_coverage"
-      run: "tox -e ${{ matrix.tox_env }}-coverage"
-
-    - name: Generate coverage report
-      if: "matrix.use_coverage"
-      run: python -m coverage xml
-
-    - name: Upload coverage to Codecov
-      if: "matrix.use_coverage"
-      uses: codecov/codecov-action@v2
-      with:
-        fail_ci_if_error: true
-        files: ./coverage.xml
-        verbose: true
-
-  deploy:
-    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'
-
-    runs-on: ubuntu-latest
-    timeout-minutes: 30
-    permissions:
-      contents: write
-
-    needs: [build]
-
-    steps:
-    - uses: actions/checkout@v2
-      with:
-        fetch-depth: 0
-        persist-credentials: false
-
-    - name: Set up Python
-      uses: actions/setup-python@v2
-      with:
-        python-version: "3.7"
-
-    - name: Install dependencies
-      run: |
-        python -m pip install --upgrade pip
-        pip install --upgrade build tox
-
-    - name: Build package
-      run: |
-        python -m build
-
-    - name: Publish package to PyPI
-      uses: pypa/gh-action-pypi-publish@master
-      with:
-        user: __token__
-        password: ${{ secrets.pypi_token }}
-
-    - name: Publish GitHub release notes
-      env:
-        GH_RELEASE_NOTES_TOKEN: ${{ github.token }}
-      run: |
-        sudo apt-get install pandoc
-        tox -e publish-gh-release-notes
diff --git a/.github/workflows/prepare-release-pr.yml b/.github/workflows/prepare-release-pr.yml
index 429834b3f21..b21ca70cb46 100644
--- a/.github/workflows/prepare-release-pr.yml
+++ b/.github/workflows/prepare-release-pr.yml
@@ -27,14 +27,16 @@ jobs:
       pull-requests: write
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v4
       with:
         fetch-depth: 0
+        # persist-credentials is needed in order for us to push the release branch.
+        persist-credentials: true
 
     - name: Set up Python
-      uses: actions/setup-python@v2
+      uses: actions/setup-python@v5
       with:
-        python-version: "3.8"
+        python-version: "3.x"
 
     - name: Install dependencies
       run: |
@@ -43,10 +45,18 @@ jobs:
 
     - name: Prepare release PR (minor/patch release)
       if: github.event.inputs.major == 'no'
+      env:
+        BRANCH: ${{ github.event.inputs.branch }}
+        PRERELEASE: ${{ github.event.inputs.prerelease }}
+        GH_TOKEN: ${{ github.token }}
       run: |
-        tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ github.token }} --prerelease='${{ github.event.inputs.prerelease }}'
+        tox -e prepare-release-pr -- "$BRANCH" --prerelease="$PRERELEASE"
 
     - name: Prepare release PR (major release)
       if: github.event.inputs.major == 'yes'
+      env:
+        BRANCH: ${{ github.event.inputs.branch }}
+        PRERELEASE: ${{ github.event.inputs.prerelease }}
+        GH_TOKEN: ${{ github.token }}
       run: |
-        tox -e prepare-release-pr -- ${{ github.event.inputs.branch }} ${{ github.token }} --major --prerelease='${{ github.event.inputs.prerelease }}'
+        tox -e prepare-release-pr -- "$BRANCH" --major --prerelease="$PRERELEASE"
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 00000000000..82f9a1f2579
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -0,0 +1,23 @@
+name: close needs-information issues
+on:
+  schedule:
+    - cron: "30 1 * * *"
+  workflow_dispatch:
+
+jobs:
+  close-issues:
+    runs-on: ubuntu-latest
+    permissions:
+      issues: write
+    steps:
+      - uses: actions/stale@v9
+        with:
+          debug-only: false
+          days-before-issue-stale: 14
+          days-before-issue-close: 7
+          only-labels: "status: needs information"
+          stale-issue-label: "stale"
+          stale-issue-message: "This issue is stale because it has the `status: needs information` label and requested follow-up information was not provided for 14 days."
+          close-issue-message: "This issue was closed because it has the `status: needs information` label and follow-up information has not been provided for 7 days since being marked as stale."
+          days-before-pr-stale: -1
+          days-before-pr-close: -1
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000000..44c9d68c03d
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,283 @@
+name: test
+
+on:
+  push:
+    branches:
+      - main
+      - "[0-9]+.[0-9]+.x"
+      - "test-me-*"
+    tags:
+      - "[0-9]+.[0-9]+.[0-9]+"
+      - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
+
+  pull_request:
+    branches:
+      - main
+      - "[0-9]+.[0-9]+.x"
+    types:
+    - opened  # default
+    - synchronize  # default
+    - reopened  # default
+    - ready_for_review  # used in PRs created from the release workflow
+
+env:
+  PYTEST_ADDOPTS: "--color=yes"
+
+# Cancel running jobs for the same workflow and branch.
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+# Set permissions at the job level.
+permissions: {}
+
+jobs:
+  package:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v4
+      with:
+        fetch-depth: 0
+        persist-credentials: false
+    - name: Build and Check Package
+      uses: hynek/build-and-inspect-python-package@v2.12.0
+
+  build:
+    needs: [package]
+
+    runs-on: ${{ matrix.os }}
+    timeout-minutes: 45
+    permissions:
+      contents: read
+
+    strategy:
+      fail-fast: false
+      matrix:
+        name: [
+          "windows-py39-unittestextras",
+          "windows-py39-pluggy",
+          "windows-py39-xdist",
+          "windows-py310",
+          "windows-py311",
+          "windows-py312",
+          "windows-py313",
+
+          "ubuntu-py39-lsof-numpy-pexpect",
+          "ubuntu-py39-pluggy",
+          "ubuntu-py39-freeze",
+          "ubuntu-py39-xdist",
+          "ubuntu-py310-xdist",
+          "ubuntu-py311",
+          "ubuntu-py312",
+          "ubuntu-py313-pexpect",
+          "ubuntu-pypy3-xdist",
+
+          "macos-py39",
+          "macos-py310",
+          "macos-py312",
+          "macos-py313",
+
+          "doctesting",
+          "plugins",
+        ]
+
+        include:
+          - name: "windows-py39-unittestextras"
+            python: "3.9"
+            os: windows-latest
+            tox_env: "py39-unittestextras"
+            use_coverage: true
+
+          - name: "windows-py39-pluggy"
+            python: "3.9"
+            os: windows-latest
+            tox_env: "py39-pluggymain-pylib-xdist"
+
+          - name: "windows-py39-xdist"
+            python: "3.9"
+            os: windows-latest
+            tox_env: "py39-xdist"
+
+          - name: "windows-py310"
+            python: "3.10"
+            os: windows-latest
+            tox_env: "py310-xdist"
+
+          - name: "windows-py311"
+            python: "3.11"
+            os: windows-latest
+            tox_env: "py311"
+
+          - name: "windows-py312"
+            python: "3.12"
+            os: windows-latest
+            tox_env: "py312"
+
+          - name: "windows-py313"
+            python: "3.13"
+            os: windows-latest
+            tox_env: "py313"
+
+
+          - name: "ubuntu-py39-lsof-numpy-pexpect"
+            python: "3.9"
+            os: ubuntu-latest
+            tox_env: "py39-lsof-numpy-pexpect"
+            use_coverage: true
+
+          - name: "ubuntu-py39-pluggy"
+            python: "3.9"
+            os: ubuntu-latest
+            tox_env: "py39-pluggymain-pylib-xdist"
+
+          - name: "ubuntu-py39-freeze"
+            python: "3.9"
+            os: ubuntu-latest
+            tox_env: "py39-freeze"
+
+          - name: "ubuntu-py39-xdist"
+            python: "3.9"
+            os: ubuntu-latest
+            tox_env: "py39-xdist"
+
+          - name: "ubuntu-py310-xdist"
+            python: "3.10"
+            os: ubuntu-latest
+            tox_env: "py310-xdist"
+
+          - name: "ubuntu-py311"
+            python: "3.11"
+            os: ubuntu-latest
+            tox_env: "py311"
+            use_coverage: true
+
+          - name: "ubuntu-py312"
+            python: "3.12"
+            os: ubuntu-latest
+            tox_env: "py312"
+            use_coverage: true
+
+          - name: "ubuntu-py313-pexpect"
+            python: "3.13"
+            os: ubuntu-latest
+            tox_env: "py313-pexpect"
+            use_coverage: true
+
+          - name: "ubuntu-pypy3-xdist"
+            python: "pypy-3.9"
+            os: ubuntu-latest
+            tox_env: "pypy3-xdist"
+
+
+          - name: "macos-py39"
+            python: "3.9"
+            os: macos-latest
+            tox_env: "py39-xdist"
+            use_coverage: true
+
+          - name: "macos-py310"
+            python: "3.10"
+            os: macos-latest
+            tox_env: "py310-xdist"
+
+          - name: "macos-py312"
+            python: "3.12"
+            os: macos-latest
+            tox_env: "py312-xdist"
+
+          - name: "macos-py313"
+            python: "3.13"
+            os: macos-latest
+            tox_env: "py313-xdist"
+
+
+          - name: "plugins"
+            python: "3.12"
+            os: ubuntu-latest
+            tox_env: "plugins"
+
+
+          - name: "doctesting"
+            python: "3.9"
+            os: ubuntu-latest
+            tox_env: "doctesting"
+            use_coverage: true
+
+    continue-on-error: >-
+      ${{
+        contains(
+          fromJSON(
+            '[
+              "windows-py39-pluggy",
+              "windows-py313",
+              "ubuntu-py39-pluggy",
+              "ubuntu-py39-freeze",
+              "ubuntu-py313",
+              "macos-py39",
+              "macos-py313"
+            ]'
+          ),
+          matrix.name
+        )
+        && true
+        || false
+      }}
+
+    steps:
+    - uses: actions/checkout@v4
+      with:
+        fetch-depth: 0
+        persist-credentials: false
+
+    - name: Download Package
+      uses: actions/download-artifact@v4
+      with:
+        name: Packages
+        path: dist
+
+    - name: Set up Python ${{ matrix.python }}
+      uses: actions/setup-python@v5
+      with:
+        python-version: ${{ matrix.python }}
+        check-latest: ${{ endsWith(matrix.python, '-dev') }}
+
+    - name: Install dependencies
+      run: |
+        python -m pip install --upgrade pip
+        pip install tox coverage
+
+    - name: Test without coverage
+      if: "! matrix.use_coverage"
+      shell: bash
+      run: tox run -e ${{ matrix.tox_env }} --installpkg `find dist/*.tar.gz`
+
+    - name: Test with coverage
+      if: "matrix.use_coverage"
+      shell: bash
+      run: tox run -e ${{ matrix.tox_env }}-coverage --installpkg `find dist/*.tar.gz`
+
+    - name: Generate coverage report
+      if: "matrix.use_coverage"
+      run: python -m coverage xml
+
+    - name: Upload coverage to Codecov
+      if: "matrix.use_coverage"
+      uses: codecov/codecov-action@v5
+      with:
+        fail_ci_if_error: false
+        files: ./coverage.xml
+        verbose: true
+
+  check:  # This job does nothing and is only used for the branch protection
+    if: always()
+
+    needs:
+    - build
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Decide whether the needed jobs succeeded or failed
+      uses: re-actors/alls-green@223e4bb7a751b91f43eda76992bcfbf23b8b0302
+      with:
+        jobs: ${{ toJSON(needs) }}
diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml
index 17c6364f4bd..c10aefa3a55 100644
--- a/.github/workflows/update-plugin-list.yml
+++ b/.github/workflows/update-plugin-list.yml
@@ -11,7 +11,8 @@ on:
 permissions: {}
 
 jobs:
-  createPullRequest:
+  update-plugin-list:
+    if: github.repository_owner == 'pytest-dev'
     runs-on: ubuntu-latest
     permissions:
       contents: write
@@ -19,25 +20,35 @@ jobs:
 
     steps:
       - name: Checkout
-        uses: actions/checkout@v2
+        uses: actions/checkout@v4
         with:
           fetch-depth: 0
+          persist-credentials: false
 
       - name: Setup Python
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v5
         with:
-          python-version: 3.8
+          python-version: "3.11"
+          cache: pip
+
+      - name: requests-cache
+        uses: actions/cache@v4
+        with:
+          path: ~/.cache/pytest-plugin-list/
+          key: plugins-http-cache-${{ github.run_id }} # Can use time based key as well
+          restore-keys: plugins-http-cache-
 
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip
-          pip install packaging requests tabulate[widechars] tqdm
+          pip install packaging requests tabulate[widechars] tqdm requests-cache platformdirs
 
       - name: Update Plugin List
         run: python scripts/update-plugin-list.py
 
       - name: Create Pull Request
-        uses: peter-evans/create-pull-request@2455e1596942c2902952003bbb574afbbe2ab2e6
+        id: pr
+        uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
         with:
           commit-message: '[automated] Update plugin list'
           author: 'pytest bot <pytestbot@users.noreply.github.com>'
@@ -46,3 +57,14 @@ jobs:
           branch-suffix: short-commit-hash
           title: '[automated] Update plugin list'
           body: '[automated] Update plugin list'
+          draft: true
+
+      - name: Instruct the maintainers to trigger CI by undrafting the PR
+        env:
+          GITHUB_TOKEN: ${{ github.token }}
+          PULL_REQUEST_NUMBER: ${{ steps.pr.outputs.pull-request-number }}
+        run: >-
+          gh pr comment
+          --body 'Please mark the PR as ready for review to trigger PR checks.'
+          --repo '${{ github.repository }}'
+          "$PULL_REQUEST_NUMBER"
diff --git a/.gitignore b/.gitignore
index 935da3b9a2e..c4557b33a1c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,7 +25,6 @@ src/_pytest/_version.py
 
 doc/*/_build
 doc/*/.doctrees
-doc/*/_changelog_towncrier_draft.rst
 build/
 dist/
 *.egg-info
@@ -50,6 +49,8 @@ coverage.xml
 .project
 .settings
 .vscode
+__pycache__/
+.python-version
 
 # generated by pip
 pip-wheel-metadata/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 20cede3b7bb..3573c876d9e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,68 +1,77 @@
 repos:
--   repo: https://github.com/psf/black
-    rev: 21.11b1
-    hooks:
-    -   id: black
-        args: [--safe, --quiet]
--   repo: https://github.com/asottile/blacken-docs
-    rev: v1.12.0
-    hooks:
-    -   id: blacken-docs
-        additional_dependencies: [black==20.8b1]
+- repo: https://github.com/astral-sh/ruff-pre-commit
+  rev: "v0.11.4"
+  hooks:
+    - id: ruff
+      args: ["--fix"]
+    - id: ruff-format
 -   repo: https://github.com/pre-commit/pre-commit-hooks
-    rev: v4.0.1
+    rev: v5.0.0
     hooks:
     -   id: trailing-whitespace
     -   id: end-of-file-fixer
-    -   id: fix-encoding-pragma
-        args: [--remove]
     -   id: check-yaml
-    -   id: debug-statements
-        exclude: _pytest/(debugging|hookspec).py
-        language_version: python3
--   repo: https://github.com/PyCQA/flake8
-    rev: 4.0.1
-    hooks:
-    -   id: flake8
-        language_version: python3
-        additional_dependencies:
-          - flake8-typing-imports==1.9.0
-          - flake8-docstrings==1.5.0
--   repo: https://github.com/asottile/reorder_python_imports
-    rev: v2.6.0
+- repo: https://github.com/woodruffw/zizmor-pre-commit
+  rev: v1.5.2
+  hooks:
+    - id: zizmor
+-   repo: https://github.com/adamchainz/blacken-docs
+    rev: 1.19.1
     hooks:
-    -   id: reorder-python-imports
-        args: ['--application-directories=.:src', --py36-plus]
--   repo: https://github.com/asottile/pyupgrade
-    rev: v2.29.1
-    hooks:
-    -   id: pyupgrade
-        args: [--py36-plus]
--   repo: https://github.com/asottile/setup-cfg-fmt
-    rev: v1.20.0
+    -   id: blacken-docs
+        additional_dependencies: [black==24.1.1]
+-   repo: https://github.com/codespell-project/codespell
+    rev: v2.4.1
     hooks:
-    -   id: setup-cfg-fmt
-        args: [--max-py-version=3.10]
+    -   id: codespell
+        args: ["--toml=pyproject.toml"]
+        additional_dependencies:
+          - tomli
 -   repo: https://github.com/pre-commit/pygrep-hooks
-    rev: v1.9.0
+    rev: v1.10.0
     hooks:
     -   id: python-use-type-annotations
 -   repo: https://github.com/pre-commit/mirrors-mypy
-    rev: v0.910-1
+    rev: v1.15.0
     hooks:
     -   id: mypy
-        files: ^(src/|testing/)
-        args: []
+        files: ^(src/|testing/|scripts/)
         additional_dependencies:
           - iniconfig>=1.1.0
-          - py>=1.8.2
           - attrs>=19.2.0
+          - pluggy>=1.5.0
           - packaging
           - tomli
-          - types-atomicwrites
-          - types-pkg_resources
+          - types-setuptools
+          - types-tabulate
+            # for mypy running on python>=3.11 since exceptiongroup is only a dependency
+            # on <3.11
+          - exceptiongroup>=1.0.0rc8
+- repo: https://github.com/tox-dev/pyproject-fmt
+  rev: "v2.5.1"
+  hooks:
+    - id: pyproject-fmt
+      # https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
+      additional_dependencies: ["tox>=4.9"]
+-   repo: https://github.com/asottile/pyupgrade
+    rev: v3.19.1
+    hooks:
+    -   id: pyupgrade
+        args:
+          - "--py39-plus"
+        # Manual because ruff does what pyupgrade does and the two are not out of sync
+        # often enough to make launching pyupgrade everytime worth it
+        stages: [manual]
 -   repo: local
     hooks:
+    -   id: pylint
+        name: pylint
+        entry: pylint
+        language: system
+        types: [python]
+        args: ["-rn", "-sn", "--fail-on=I", "--enable-all-extentions"]
+        require_serial: true
+        stages: [manual]
     -   id: rst
         name: rst
         entry: rst-lint --encoding utf-8
@@ -72,9 +81,50 @@ repos:
     -   id: changelogs-rst
         name: changelog filenames
         language: fail
-        entry: 'changelog files must be named ####.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst'
-        exclude: changelog/(\d+\.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst|README.rst|_template.rst)
+        entry: >-
+          changelog files must be named
+          ####.(
+          breaking
+          | deprecation
+          | feature
+          | improvement
+          | bugfix
+          | vendor
+          | doc
+          | packaging
+          | contrib
+          | misc
+          )(.#)?(.rst)?
+        exclude: >-
+          (?x)
+          ^
+            changelog/(
+              \.gitignore
+              |\d+\.(
+                breaking
+                |deprecation
+                |feature
+                |improvement
+                |bugfix
+                |vendor
+                |doc
+                |packaging
+                |contrib
+                |misc
+              )(\.\d+)?(\.rst)?
+              |README\.rst
+              |_template\.rst
+            )
+          $
         files: ^changelog/
+    -   id: changelogs-user-role
+        name: Changelog files should use a non-broken :user:`name` role
+        language: pygrep
+        entry: :user:([^`]+`?|`[^`]+[\s,])
+        pass_filenames: true
+        types:
+          - file
+          - rst
     -   id: py-deprecated
         name: py library is deprecated
         language: pygrep
@@ -93,7 +143,7 @@ repos:
         types: [python]
     -   id: py-path-deprecated
         name: py.path usage is deprecated
-        exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py
+        exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py|src/_pytest/legacypath.py
         language: pygrep
         entry: \bpy\.path\.local
         types: [python]
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 00000000000..f7370f1bb98
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,31 @@
+version: 2
+
+python:
+   install:
+     # Install pytest first, then doc/en/requirements.txt.
+     # This order is important to honor any pins in doc/en/requirements.txt
+     # when the pinned library is also a dependency of pytest.
+     - method: pip
+       path: .
+     - requirements: doc/en/requirements.txt
+
+sphinx:
+  configuration: doc/en/conf.py
+  fail_on_warning: true
+
+build:
+  os: ubuntu-24.04
+  tools:
+    python: >-
+      3.12
+  apt_packages:
+    - inkscape
+  jobs:
+    post_checkout:
+      - git fetch --unshallow || true
+      - git fetch --tags || true
+
+formats:
+  - epub
+  - pdf
+  - htmlzip
diff --git a/.readthedocs.yml b/.readthedocs.yml
deleted file mode 100644
index bc44d38b4c7..00000000000
--- a/.readthedocs.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-version: 2
-
-python:
-   install:
-      - requirements: doc/en/requirements.txt
-      - method: pip
-        path: .
-
-build:
-  os: ubuntu-20.04
-  tools:
-    python: "3.9"
-  apt_packages:
-    - inkscape
-
-formats:
-  - epub
-  - pdf
-  - htmlzip
diff --git a/AUTHORS b/AUTHORS
index c57502ac22b..9004008bfa5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -8,16 +8,23 @@ Abdeali JK
 Abdelrahman Elbehery
 Abhijeet Kasurde
 Adam Johnson
+Adam Stewart
 Adam Uhlir
 Ahn Ki-Wook
+Akhilesh Ramakrishnan
 Akiomi Kamakura
 Alan Velasco
+Alessio Izzo
+Alex Jones
+Alex Lambson
 Alexander Johnson
 Alexander King
 Alexei Kozlenok
+Alice Purcell
 Allan Feldman
 Aly Sivji
 Amir Elkess
+Ammar Askar
 Anatoly Bubenkoff
 Anders Hovmöller
 Andras Mitzki
@@ -25,16 +32,19 @@ Andras Tim
 Andrea Cimatoribus
 Andreas Motl
 Andreas Zeidler
+Andrew Pikul
 Andrew Shapton
 Andrey Paramonov
 Andrzej Klajnert
 Andrzej Ostrowski
 Andy Freeland
+Anita Hammer
 Anthon van der Neut
 Anthony Shaw
 Anthony Sottile
 Anton Grinevich
 Anton Lodder
+Anton Zhilin
 Antony Lee
 Arel Cordero
 Arias Emmanuel
@@ -42,19 +52,30 @@ Ariel Pillemer
 Armin Rigo
 Aron Coyle
 Aron Curzon
+Arthur Richard
+Ashish Kurmi
+Ashley Whetter
 Aviral Verma
 Aviv Palivoda
+Babak Keyvani
+Bahram Farahmand
 Barney Gale
+Ben Brown
 Ben Gartner
+Ben Leith
 Ben Webb
 Benjamin Peterson
+Benjamin Schubert
 Bernard Pratz
+Bo Wu
 Bob Ippolito
 Brian Dorsey
+Brian Larsen
 Brian Maissy
 Brian Okken
 Brianna Laugher
 Bruno Oliveira
+Cal Jacobson
 Cal Leeming
 Carl Friedrich Bolz
 Carlos Jenkins
@@ -62,10 +83,14 @@ Ceridwen
 Charles Cloud
 Charles Machalow
 Charnjit SiNGH (CCSJ)
+Cheuk Ting Ho
+Chris Mahoney
 Chris Lamb
 Chris NeJame
 Chris Rose
+Chris Wheeler
 Christian Boelsen
+Christian Clauss
 Christian Fetzer
 Christian Neumüller
 Christian Theunert
@@ -74,15 +99,22 @@ Christine Mecklenborg
 Christoph Buelter
 Christopher Dignam
 Christopher Gilling
+Christopher Head
 Claire Cecil
 Claudio Madotto
+Clément M.T. Robert
+Cornelius Riemenschneider
 CrazyMerlyn
 Cristian Vera
 Cyrus Maden
+Daara Shaw
 Damian Skrzypczak
 Daniel Grana
 Daniel Hahler
+Daniel Miller
 Daniel Nuri
+Daniel Sánchez Castelló
+Daniel Valenzuela Zenteno
 Daniel Wandschneider
 Daniele Procida
 Danielle Jenkins
@@ -91,12 +123,14 @@ Dave Hunt
 David Díaz-Barquero
 David Mohr
 David Paul Röthlisberger
+David Peled
 David Szotten
 David Vierra
 Daw-Ran Liou
 Debi Mishra
 Denis Kirisov
 Denivy Braiam Rück
+Dheeraj C K
 Dhiren Serai
 Diego Russo
 Dmitry Dygalo
@@ -107,23 +141,37 @@ Edison Gustavo Muenz
 Edoardo Batini
 Edson Tadeu M. Manoel
 Eduardo Schettino
+Edward Haigh
+Eero Vaher
 Eli Boyarski
 Elizaveta Shashkova
 Éloi Rivard
+Emil Hjelm
 Endre Galaczi
 Eric Hunsberger
 Eric Liu
 Eric Siegerman
+Eric Yuan
 Erik Aronesty
+Erik Hasse
 Erik M. Bray
+Ethan Wass
 Evan Kepner
+Evgeny Seliverstov
+Fabian Sturm
 Fabien Zarifian
 Fabio Zadrozny
+Farbod Ahmadian
+faph
+Felix Hofstätter
 Felix Nieuwenhuizen
 Feng Ma
 Florian Bruhin
 Florian Dahlitz
 Floris Bruynooghe
+Frank Hoffmann
+Fraser Stark
+Gabriel Landau
 Gabriel Reis
 Garvit Shubham
 Gene Wood
@@ -149,11 +197,16 @@ Ian Bicking
 Ian Lesperance
 Ilya Konstantinov
 Ionuț Turturică
+Isaac Virshup
+Israel Fruchter
+Itxaso Aizpurua
 Iwan Briquemont
 Jaap Broekhuizen
+Jake VanderPlas
 Jakob van Santen
 Jakub Mitoraj
 James Bourbeau
+James Frost
 Jan Balster
 Janne Vanhala
 Jason R. Coombs
@@ -162,52 +215,72 @@ Javier Romero
 Jeff Rackauckas
 Jeff Widman
 Jenni Rinker
+Jens Tröger
+Jiajun Xu
 John Eddie Ayson
+John Litborn
 John Towler
+Jon Parise
 Jon Sonesen
 Jonas Obrist
 Jordan Guymon
 Jordan Moldow
 Jordan Speicher
 Joseph Hunkeler
+Joseph Sawaya
 Josh Karpel
 Joshua Bronson
+Julian Valentin
 Jurko Gospodnetić
-Justyna Janczyszyn
 Justice Ndou
+Justyna Janczyszyn
 Kale Kundert
 Kamran Ahmad
+Kenny Y
 Karl O. Pinc
 Karthikeyan Singaravelan
 Katarzyna Jachim
 Katarzyna Król
 Katerina Koukiou
 Keri Volans
+Kevin C
 Kevin Cox
+Kevin Hierro Carrasco
 Kevin J. Foley
+Kian Eliasi
+Kian-Meng Ang
 Kodi B. Arfer
+Kojo Idrissa
 Kostis Anagnostopoulos
 Kristoffer Nordström
 Kyle Altendorf
 Lawrence Mitchell
 Lee Kamentsky
+Leonardus Chen
 Lev Maximov
+Levon Saldamli
 Lewis Cowles
 Llandy Riveron Del Risco
 Loic Esteve
+lovetheguitar
 Lukas Bednar
 Luke Murphy
 Maciek Fijalkowski
+Maggie Chung
 Maho
 Maik Figura
 Mandeep Bhutani
 Manuel Krebber
+Marc Mueller
 Marc Schlaich
 Marcelo Duarte Trevisani
 Marcin Bachry
+Marc Bresson
 Marco Gorelli
 Mark Abramowitz
 Mark Dickinson
+Mark Vong
+Marko Pacak
 Markus Unterwaditzer
 Martijn Faassen
 Martin Altmayer
@@ -221,31 +294,43 @@ Matthias Hafner
 Maxim Filipenko
 Maximilian Cosmo Sitter
 mbyt
-Mickey Pashov
 Michael Aquilina
 Michael Birtwell
 Michael Droettboom
 Michael Goerz
 Michael Krebs
 Michael Seifert
+Michael Vogt
 Michal Wajszczuk
+Michał Górny
 Michał Zięba
+Mickey Pashov
 Mihai Capotă
+Mihail Milushev
 Mike Hoyle (hoylemd)
 Mike Lundy
+Milan Lesnek
 Miro Hrončok
+mrbean-bremen
+Nathan Goldbaum
+Nathan Rousseau
 Nathaniel Compton
 Nathaniel Waisbrot
+Nauman Ahmed
 Ned Batchelder
+Neil Martin
 Neven Mundar
 Nicholas Devenish
 Nicholas Murphy
 Niclas Olofsson
 Nicolas Delaby
+Nicolas Simonds
+Nico Vidal
 Nikolay Kondratyev
-Olga Matoula
+Nipunn Koorapati
 Oleg Pidsadnyi
 Oleg Sushchenko
+Olga Matoula
 Oliver Bestwalter
 Omar Kohl
 Omer Hadari
@@ -253,30 +338,42 @@ Ondřej Súkup
 Oscar Benjamin
 Parth Patel
 Patrick Hayes
+Patrick Lannigan
+Paul Müller
+Paul Reece
 Pauli Virtanen
 Pavel Karateev
+Pavel Zhukov
 Paweł Adamczak
 Pedro Algarvio
 Petter Strandmark
 Philipp Loose
+Pierre Sassoulas
 Pieter Mulder
 Piotr Banaszkiewicz
 Piotr Helm
+Poulami Sau
 Prakhar Gurunani
 Prashant Anand
 Prashant Sharma
 Pulkit Goyal
 Punyashloka Biswal
 Quentin Pradet
+q0w
 Ralf Schmitt
-Ram Rachum
 Ralph Giles
+Ram Rachum
 Ran Benita
 Raphael Castaneda
 Raphael Pierzina
+Rafal Semik
+Reza Mousavi
 Raquel Alegre
 Ravi Chandra
+Reagan Lee
+Rob Arrow
 Robert Holt
+Roberto Aldera
 Roberto Polli
 Roland Puntaier
 Romain Dorgueil
@@ -285,59 +382,90 @@ Ronny Pfannschmidt
 Ross Lawley
 Ruaridh Williamson
 Russel Winder
+Russell Martin
+Ryan Puddephatt
 Ryan Wooden
+Sadra Barikbin
 Saiprasad Kale
+Samuel Colvin
 Samuel Dion-Girardeau
+Samuel Jirovec
 Samuel Searles-Bryant
+Samuel Therrien (Avasam)
 Samuele Pedroni
 Sanket Duthade
 Sankt Petersbug
+Saravanan Padmanaban
+Sean Malloy
 Segev Finer
 Serhii Mozghovyi
 Seth Junot
 Shantanu Jain
+Sharad Nair
+Shaygan Hooshyari
 Shubham Adep
+Simon Blanchard
 Simon Gomizelj
+Simon Holesch
 Simon Kerr
 Skylar Downes
 Srinivas Reddy Thatiparthy
+Stefaan Lippens
 Stefan Farmbauer
 Stefan Scherfke
 Stefan Zimmermann
+Stefanie Molin
 Stefano Taschini
 Steffen Allner
 Stephan Obermann
+Sven
 Sven-Hendrik Haase
+Sviatoslav Sydorenko
 Sylvain Marié
 Tadek Teleżyński
 Takafumi Arakaki
+Takumi Otani
 Taneli Hukkinen
 Tanvi Mehta
+Tanya Agarwal
 Tarcisio Fischer
 Tareq Alayan
+Tatiana Ovary
 Ted Xiao
 Terje Runde
 Thomas Grainger
 Thomas Hisch
+Tianyu Dongfang
 Tim Hoffmann
 Tim Strazny
+TJ Bruno
+Tobias Diez
+Tobias Petersen
 Tom Dalton
 Tom Viner
 Tomáš Gavenčiak
 Tomer Keren
+Tony Narlock
 Tor Colvin
 Trevor Bekolay
+Tushar Sadhwani
 Tyler Goodlet
+Tyler Smart
 Tzu-ping Chung
 Vasily Kuznetsov
 Victor Maryama
+Victor Rodriguez
 Victor Uriarte
 Vidar T. Fauske
+Vijay Arora
+Virendra Patil
 Virgil Dupras
 Vitaly Lashmanov
+Vivaan Verma
 Vlad Dragos
 Vlad Radziuk
 Vladyslav Rachek
+Volodymyr Kochetkov
 Volodymyr Piskun
 Wei Lin
 Wil Cooley
@@ -347,8 +475,17 @@ Wouter van Ackooy
 Xixi Zhao
 Xuan Luong
 Xuecong Liao
+Yannick Péroux
+Yao Xiao
 Yoav Caspi
+Yuliang Shao
+Yusuke Kadowaki
+Yutian Li
+Yuval Shimon
 Zac Hatfield-Dodds
+Zach Snicker
 Zachary Kneupper
+Zachary OBrien
+Zhouxin Qiu
 Zoltán Máté
 Zsolt Cserna
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 24bca723c8b..56824a43ff4 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -1,14 +1,10 @@
 ============================
-Contribution getting started
+Contributing
 ============================
 
 Contributions are highly welcomed and appreciated.  Every little bit of help counts,
 so do not hesitate!
 
-.. contents::
-   :depth: 2
-   :backlinks: none
-
 
 .. _submitfeedback:
 
@@ -50,6 +46,8 @@ Fix bugs
 --------
 
 Look through the `GitHub issues for bugs <https://github.com/pytest-dev/pytest/labels/type:%20bug>`_.
+See also the `"good first issue" issues <https://github.com/pytest-dev/pytest/labels/good%20first%20issue>`_
+that are friendly to new contributors.
 
 :ref:`Talk <contact>` to developers to find out how you can fix specific bugs. To indicate that you are going
 to work on a particular issue, add a comment to that effect on the specific issue.
@@ -126,7 +124,7 @@ For example:
 Submitting Plugins to pytest-dev
 --------------------------------
 
-Pytest development of the core, some plugins and support code happens
+Development of the pytest core, support code, and some plugins happens
 in repositories living under the ``pytest-dev`` organisations:
 
 - `pytest-dev on GitHub <https://github.com/pytest-dev>`_
@@ -195,11 +193,12 @@ Short version
 ~~~~~~~~~~~~~
 
 #. Fork the repository.
+#. Fetch tags from upstream if necessary (if you cloned only main `git fetch --tags https://github.com/pytest-dev/pytest`).
 #. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed.
-#. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting.
+#. Follow `PEP-8 <https://www.python.org/dev/peps/pep-0008/>`_ for naming.
 #. Tests are run using ``tox``::
 
-    tox -e linting,py37
+    tox -e linting,py39
 
    The test environments above are usually enough to cover most cases locally.
 
@@ -221,7 +220,7 @@ changes you want to review and merge.  Pull requests are stored on
 Once you send a pull request, we can discuss its potential modifications and
 even add more commits to it later on. There's an excellent tutorial on how Pull
 Requests work in the
-`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_.
+`GitHub Help Center <https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests>`_.
 
 Here is a simple overview, with pytest-specific bits:
 
@@ -234,6 +233,7 @@ Here is a simple overview, with pytest-specific bits:
 
     $ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
     $ cd pytest
+    $ git fetch --tags https://github.com/pytest-dev/pytest
     # now, create your own branch off "main":
 
         $ git checkout -b your-bugfix-branch-name main
@@ -242,6 +242,11 @@ Here is a simple overview, with pytest-specific bits:
    be released in micro releases whereas features will be released in
    minor releases and incompatible changes in major releases.
 
+   You will need the tags to test locally, so be sure you have the tags from the main repository. If you suspect you don't, set the main repository as upstream and fetch the tags::
+
+     $ git remote add upstream https://github.com/pytest-dev/pytest
+     $ git fetch upstream --tags
+
    If you need some help with Git, follow this quick start
    guide: https://git.wiki.kernel.org/index.php/QuickStart
 
@@ -265,35 +270,35 @@ Here is a simple overview, with pytest-specific bits:
 
 #. Run all the tests
 
-   You need to have Python 3.7 available in your system.  Now
+   You need to have Python 3.9 or later available in your system.  Now
    running tests is as simple as issuing this command::
 
-    $ tox -e linting,py37
+    $ tox -e linting,py39
 
-   This command will run tests via the "tox" tool against Python 3.7
+   This command will run tests via the "tox" tool against Python 3.9
    and also perform "lint" coding-style checks.
 
-#. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 for naming.
+#. You can now edit your local working copy and run the tests again as necessary. Please follow `PEP-8 <https://www.python.org/dev/peps/pep-0008/>`_ for naming.
 
-   You can pass different options to ``tox``. For example, to run tests on Python 3.7 and pass options to pytest
+   You can pass different options to ``tox``. For example, to run tests on Python 3.9 and pass options to pytest
    (e.g. enter pdb on failure) to pytest you can do::
 
-    $ tox -e py37 -- --pdb
+    $ tox -e py39 -- --pdb
 
-   Or to only run tests in a particular test module on Python 3.7::
+   Or to only run tests in a particular test module on Python 3.9::
 
-    $ tox -e py37 -- testing/test_config.py
+    $ tox -e py39 -- testing/test_config.py
 
 
    When committing, ``pre-commit`` will re-format the files if necessary.
 
 #. If instead of using ``tox`` you prefer to run the tests directly, then we suggest to create a virtual environment and use
-   an editable install with the ``testing`` extra::
+   an editable install with the ``dev`` extra::
 
        $ python3 -m venv .venv
        $ source .venv/bin/activate  # Linux
        $ .venv/Scripts/activate.bat  # Windows
-       $ pip install -e ".[testing]"
+       $ pip install -e ".[dev]"
 
    Afterwards, you can edit the files and run pytest normally::
 
@@ -375,10 +380,61 @@ pull requests from other contributors yourself after having reviewed
 them.
 
 
+Merge/squash guidelines
+-----------------------
+
+When a PR is approved and ready to be integrated to the ``main`` branch, one has the option to *merge* the commits unchanged, or *squash* all the commits into a single commit.
+
+Here are some guidelines on how to proceed, based on examples of a single PR commit history:
+
+1. Miscellaneous commits:
+
+   * ``Implement X``
+   * ``Fix test_a``
+   * ``Add myself to AUTHORS``
+   * ``fixup! Fix test_a``
+   * ``Update tests/test_integration.py``
+   * ``Merge origin/main into PR branch``
+   * ``Update tests/test_integration.py``
+
+   In this case, prefer to use the **Squash** merge strategy: the commit history is a bit messy (not in a derogatory way, often one just commits changes because they know the changes will eventually be squashed together), so squashing everything into a single commit is best. You must clean up the commit message, making sure it contains useful details.
+
+2. Separate commits related to the same topic:
+
+   * ``Implement X``
+   * ``Add myself to AUTHORS``
+   * ``Update CHANGELOG for X``
+
+   In this case, prefer to use the **Squash** merge strategy: while the commit history is not "messy" as in the example above, the individual commits do not bring much value overall, specially when looking at the changes a few months/years down the line.
+
+3. Separate commits, each with their own topic (refactorings, renames, etc), but still have a larger topic/purpose.
+
+   * ``Refactor class X in preparation for feature Y``
+   * ``Remove unused method``
+   * ``Implement feature Y``
+
+   In this case, prefer to use the **Merge** strategy: each commit is valuable on its own, even if they serve a common topic overall. Looking at the history later, it is useful to have the removal of the unused method separately on its own commit, along with more information (such as how it became unused in the first place).
+
+4. Separate commits, each with their own topic, but without a larger topic/purpose other than improve the code base (using more modern techniques, improve typing, removing clutter, etc).
+
+   * ``Improve internal names in X``
+   * ``Add type annotations to Y``
+   * ``Remove unnecessary dict access``
+   * ``Remove unreachable code due to EOL Python``
+
+   In this case, prefer to use the **Merge** strategy: each commit is valuable on its own, and the information on each is valuable in the long term.
+
+
+As mentioned, those are overall guidelines, not rules cast in stone. This topic was discussed in `#12633 <https://github.com/pytest-dev/pytest/discussions/12633>`_.
+
+
+*Backport PRs* (as those created automatically from a ``backport`` label) should always be **squashed**, as they preserve the original PR author.
+
+
 Backporting bug fixes for the next patch release
 ------------------------------------------------
 
-Pytest makes feature release every few weeks or months. In between, patch releases
+Pytest makes a feature release every few weeks or months. In between, patch releases
 are made to the previous feature release, containing bug fixes only. The bug fixes
 usually fix regressions, but may be any change that should reach users before the
 next feature release.
@@ -387,10 +443,17 @@ Suppose for example that the latest release was 1.2.3, and you want to include
 a bug fix in 1.2.4 (check https://github.com/pytest-dev/pytest/releases for the
 actual latest release). The procedure for this is:
 
-#. First, make sure the bug is fixed the ``main`` branch, with a regular pull
+#. First, make sure the bug is fixed in the ``main`` branch, with a regular pull
    request, as described above. An exception to this is if the bug fix is not
    applicable to ``main`` anymore.
 
+Automatic method:
+
+Add a ``backport 1.2.x`` label to the PR you want to backport. This will create
+a backport PR against the ``1.2.x`` branch.
+
+Manual method:
+
 #. ``git checkout origin/1.2.x -b backport-XXXX`` # use the main PR number here
 
 #. Locate the merge commit on the PR, in the *merged* message, for example:
@@ -426,6 +489,8 @@ above?
 All the above are not rules, but merely some guidelines/suggestions on what we should expect
 about backports.
 
+Backports should be **squashed** (rather than **merged**), as doing so preserves the original PR author correctly.
+
 Handling stale issues/PRs
 -------------------------
 
@@ -473,9 +538,9 @@ When closing a Pull Request, it needs to be acknowledging the time, effort, and
 
     <bye>
 
-Closing Issues
+Closing issues
 --------------
 
 When a pull request is submitted to fix an issue, add text like ``closes #XYZW`` to the PR description and/or commits (where ``XYZW`` is the issue number). See the `GitHub docs <https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword>`_ for more information.
 
-When an issue is due to user error (e.g. misunderstanding of a functionality), please politely explain to the user why the issue raised is really a non-issue and ask them to close the issue if they have no further questions. If the original requestor is unresponsive, the issue will be handled as described in the section `Handling stale issues/PRs`_ above.
+When an issue is due to user error (e.g. misunderstanding of a functionality), please politely explain to the user why the issue raised is really a non-issue and ask them to close the issue if they have no further questions. If the original requester is unresponsive, the issue will be handled as described in the section `Handling stale issues/PRs`_ above.
diff --git a/README.rst b/README.rst
index 14733765173..091afc363da 100644
--- a/README.rst
+++ b/README.rst
@@ -20,16 +20,13 @@
     :target: https://codecov.io/gh/pytest-dev/pytest
     :alt: Code coverage Status
 
-.. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg
-    :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain
+.. image:: https://github.com/pytest-dev/pytest/actions/workflows/test.yml/badge.svg
+    :target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Atest
 
 .. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/main.svg
    :target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/main
    :alt: pre-commit.ci status
 
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
-    :target: https://github.com/psf/black
-
 .. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
     :target: https://www.codetriage.com/pytest-dev/pytest
 
@@ -97,12 +94,12 @@ Features
 - `Modular fixtures <https://docs.pytest.org/en/stable/explanation/fixtures.html>`_ for
   managing small or parametrized long-lived test resources
 
-- Can run `unittest <https://docs.pytest.org/en/stable/how-to/unittest.html>`_ (or trial),
-  `nose <https://docs.pytest.org/en/stable/how-to/nose.html>`_ test suites out of the box
+- Can run `unittest <https://docs.pytest.org/en/stable/how-to/unittest.html>`_ (or trial)
+  test suites out of the box
 
-- Python 3.6+ and PyPy3
+- Python 3.9+ or PyPy3
 
-- Rich plugin architecture, with over 850+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community
+- Rich plugin architecture, with over 1300+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community
 
 
 Documentation
diff --git a/RELEASING.rst b/RELEASING.rst
index 25ce90d0f65..0ca63ee4fbf 100644
--- a/RELEASING.rst
+++ b/RELEASING.rst
@@ -37,7 +37,7 @@ breaking changes or new features.
 
 For a new minor release, first create a new maintenance branch from ``main``::
 
-     git fetch --all
+     git fetch upstream
      git branch 7.1.x upstream/main
      git push upstream 7.1.x
 
@@ -63,7 +63,7 @@ Major releases
 
 1. Create a new maintenance branch from ``main``::
 
-        git fetch --all
+        git fetch upstream
         git branch 8.0.x upstream/main
         git push upstream 8.0.x
 
@@ -133,32 +133,34 @@ Releasing
 
 Both automatic and manual processes described above follow the same steps from this point onward.
 
-#. After all tests pass and the PR has been approved, tag the release commit
-   in the ``release-MAJOR.MINOR.PATCH`` branch and push it. This will publish to PyPI::
+#. After all tests pass and the PR has been approved, trigger the ``deploy`` job
+   in https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml, using the ``release-MAJOR.MINOR.PATCH`` branch
+   as source.
 
-     git fetch --all
-     git tag MAJOR.MINOR.PATCH upstream/release-MAJOR.MINOR.PATCH
-     git push git@github.com:pytest-dev/pytest.git MAJOR.MINOR.PATCH
+   Using the command-line::
 
-   Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
+     $ gh workflow run deploy.yml -R pytest-dev/pytest --ref=release-{VERSION} -f version={VERSION}
 
-#. Merge the PR.
+   This job will require approval from ``pytest-dev/core``, after which it will publish to PyPI
+   and tag the repository.
+
+#. Merge the PR. **Make sure it's not squash-merged**, so that the tagged commit ends up in the main branch.
 
 #. Cherry-pick the CHANGELOG / announce files to the ``main`` branch::
 
-       git fetch --all --prune
+       git fetch upstream
        git checkout upstream/main -b cherry-pick-release
        git cherry-pick -x -m1 upstream/MAJOR.MINOR.x
 
 #. Open a PR for ``cherry-pick-release`` and merge it once CI passes. No need to wait for approvals if there were no conflicts on the previous step.
 
-#. For major and minor releases, tag the release cherry-pick merge commit in main with
+#. For major and minor releases (or the first prerelease of it), tag the release cherry-pick merge commit in main with
    a dev tag for the next feature release::
 
        git checkout main
        git pull
        git tag MAJOR.{MINOR+1}.0.dev0
-       git push git@github.com:pytest-dev/pytest.git MAJOR.{MINOR+1}.0.dev0
+       git push upstream MAJOR.{MINOR+1}.0.dev0
 
 #. Send an email announcement with the contents from::
 
@@ -170,4 +172,8 @@ Both automatic and manual processes described above follow the same steps from t
    * python-announce-list@python.org (all releases)
    * testing-in-python@lists.idyll.org (only major/minor releases)
 
-   And announce it on `Twitter <https://twitter.com/>`_ with the ``#pytest`` hashtag.
+   And announce it with the ``#pytest`` hashtag on:
+
+   * `Bluesky <https://bsky.app>`_
+   * `Fosstodon <https://fosstodon.org>`_
+   * `Twitter/X <https://x.com>`_
diff --git a/TIDELIFT.rst b/TIDELIFT.rst
index 2fe25841c3a..1ba246bd868 100644
--- a/TIDELIFT.rst
+++ b/TIDELIFT.rst
@@ -23,9 +23,9 @@ members of the `contributors team`_ interested in receiving funding.
 
 The current list of contributors receiving funding are:
 
-* `@asottile`_
 * `@nicoddemus`_
 * `@The-Compiler`_
+* `@RonnyPfannschmidt`_
 
 Contributors interested in receiving a part of the funds just need to submit a PR adding their
 name to the list. Contributors that want to stop receiving the funds should also submit a PR
@@ -55,6 +55,6 @@ funds. Just drop a line to one of the `@pytest-dev/tidelift-admins`_ or use the
 .. _`@pytest-dev/tidelift-admins`: https://github.com/orgs/pytest-dev/teams/tidelift-admins/members
 .. _`agreement`: https://tidelift.com/docs/lifting/agreement
 
-.. _`@asottile`: https://github.com/asottile
 .. _`@nicoddemus`: https://github.com/nicoddemus
 .. _`@The-Compiler`: https://github.com/The-Compiler
+.. _`@RonnyPfannschmidt`: https://github.com/RonnyPfannschmidt
diff --git a/bench/bench.py b/bench/bench.py
index c40fc8636c0..139c292ecd8 100644
--- a/bench/bench.py
+++ b/bench/bench.py
@@ -1,12 +1,16 @@
+from __future__ import annotations
+
 import sys
 
+
 if __name__ == "__main__":
     import cProfile
-    import pytest  # NOQA
     import pstats
 
+    import pytest  # noqa: F401
+
     script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
-    cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
+    cProfile.run(f"pytest.cmdline.main({script!r})", "prof")
     p = pstats.Stats("prof")
     p.strip_dirs()
     p.sort_stats("cumulative")
diff --git a/bench/bench_argcomplete.py b/bench/bench_argcomplete.py
index 335733df72b..468c59217df 100644
--- a/bench/bench_argcomplete.py
+++ b/bench/bench_argcomplete.py
@@ -2,8 +2,11 @@
 #                      2.7.5     3.3.2
 # FilesCompleter       75.1109   69.2116
 # FastFilesCompleter    0.7383    1.0760
+from __future__ import annotations
+
 import timeit
 
+
 imports = [
     "from argcomplete.completers import FilesCompleter as completer",
     "from _pytest._argcomplete import FastFilesCompleter as completer",
diff --git a/bench/empty.py b/bench/empty.py
index 4e7371b6f80..346b79d5e33 100644
--- a/bench/empty.py
+++ b/bench/empty.py
@@ -1,2 +1,5 @@
+from __future__ import annotations
+
+
 for i in range(1000):
-    exec("def test_func_%d(): pass" % i)
+    exec(f"def test_func_{i}(): pass")
diff --git a/bench/manyparam.py b/bench/manyparam.py
index 1226c73bd9c..579f7b2488d 100644
--- a/bench/manyparam.py
+++ b/bench/manyparam.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/bench/skip.py b/bench/skip.py
index f0c9d1ddbef..9145cc0ceed 100644
--- a/bench/skip.py
+++ b/bench/skip.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
 import pytest
 
+
 SKIP = True
 
 
diff --git a/bench/unit_test.py b/bench/unit_test.py
index ad52069dbfd..0f106e16b6c 100644
--- a/bench/unit_test.py
+++ b/bench/unit_test.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
 from unittest import TestCase  # noqa: F401
 
+
 for i in range(15000):
     exec(
         f"""
diff --git a/bench/xunit.py b/bench/xunit.py
index 3a77dcdce42..31ab432441c 100644
--- a/bench/xunit.py
+++ b/bench/xunit.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+
 for i in range(5000):
     exec(
         f"""
diff --git a/changelog/.gitignore b/changelog/.gitignore
new file mode 100644
index 00000000000..3b34da34bc6
--- /dev/null
+++ b/changelog/.gitignore
@@ -0,0 +1,34 @@
+*
+!.gitignore
+!_template.rst
+!README.rst
+!*.bugfix
+!*.bugfix.rst
+!*.bugfix.*.rst
+!*.breaking
+!*.breaking.rst
+!*.breaking.*.rst
+!*.contrib
+!*.contrib.rst
+!*.contrib.*.rst
+!*.deprecation
+!*.deprecation.rst
+!*.deprecation.*.rst
+!*.doc
+!*.doc.rst
+!*.doc.*.rst
+!*.feature
+!*.feature.rst
+!*.feature.*.rst
+!*.improvement
+!*.improvement.rst
+!*.improvement.*.rst
+!*.misc
+!*.misc.rst
+!*.misc.*.rst
+!*.packaging
+!*.packaging.rst
+!*.packaging.*.rst
+!*.vendor
+!*.vendor.rst
+!*.vendor.*.rst
diff --git a/changelog/10224.improvement.rst b/changelog/10224.improvement.rst
new file mode 100644
index 00000000000..93afe9e2c1e
--- /dev/null
+++ b/changelog/10224.improvement.rst
@@ -0,0 +1,18 @@
+pytest's ``short`` and ``long`` traceback styles (:ref:`how-to-modifying-python-tb-printing`)
+now have partial :pep:`657` support and will show specific code segments in the
+traceback.
+
+.. code-block:: pytest
+
+    ================================= FAILURES =================================
+    _______________________ test_gets_correct_tracebacks _______________________
+
+    test_tracebacks.py:12: in test_gets_correct_tracebacks
+        assert manhattan_distance(p1, p2) == 1
+               ^^^^^^^^^^^^^^^^^^^^^^^^^^
+    test_tracebacks.py:6: in manhattan_distance
+        return abs(point_1.x - point_2.x) + abs(point_1.y - point_2.y)
+                               ^^^^^^^^^
+    E   AttributeError: 'NoneType' object has no attribute 'x'
+
+-- by :user:`ammaraskar`
diff --git a/changelog/10404.bugfix.rst b/changelog/10404.bugfix.rst
new file mode 100644
index 00000000000..4c98ea03d64
--- /dev/null
+++ b/changelog/10404.bugfix.rst
@@ -0,0 +1,7 @@
+Apply filterwarnings from config/cli as soon as possible, and revert them as late as possible
+so that warnings as errors are collected throughout the pytest run and before the
+unraisable and threadexcept hooks are removed.
+
+This allows very late warnings and unraisable/threadexcept exceptions to fail the test suite.
+
+This also changes the warning that the lsof plugin issues from PytestWarning to the new warning PytestFDWarning so it can be more easily filtered.
diff --git a/changelog/10839.deprecation.rst b/changelog/10839.deprecation.rst
new file mode 100644
index 00000000000..a3e2cbf51d0
--- /dev/null
+++ b/changelog/10839.deprecation.rst
@@ -0,0 +1 @@
+Requesting an asynchronous fixture without a `pytest_fixture_setup` hook that resolves it will now give a DeprecationWarning. This most commonly happens if a sync test requests an async fixture. This should have no effect on a majority of users with async tests or fixtures using async pytest plugins, but may affect non-standard hook setups or ``autouse=True``. For guidance on how to work around this warning see :ref:`sync-test-async-fixture`.
diff --git a/changelog/11067.bugfix.rst b/changelog/11067.bugfix.rst
new file mode 100644
index 00000000000..4e3cb8e7dd7
--- /dev/null
+++ b/changelog/11067.bugfix.rst
@@ -0,0 +1,3 @@
+The test report is now consistent regardless if the test xfailed via :ref:`pytest.mark.xfail <pytest.mark.xfail ref>` or :func:`pytest.fail`.
+
+Previously, *xfailed* tests via the marker would have the string ``"reason: "`` prefixed to the message, while those *xfailed* via the function did not. The prefix has been removed.
diff --git a/changelog/11118.improvement.rst b/changelog/11118.improvement.rst
new file mode 100644
index 00000000000..4760dbe9d64
--- /dev/null
+++ b/changelog/11118.improvement.rst
@@ -0,0 +1,3 @@
+Now :confval:`pythonpath` configures `$PYTHONPATH` earlier than before during the initialization process, which now also affects plugins loaded via the `-p` command-line option.
+
+-- by :user:`millerdev`
diff --git a/changelog/11372.breaking.rst b/changelog/11372.breaking.rst
new file mode 100644
index 00000000000..f4b5c3c6f6b
--- /dev/null
+++ b/changelog/11372.breaking.rst
@@ -0,0 +1 @@
+Async tests will now fail, instead of warning+skipping, if you don't have any suitable plugin installed.
diff --git a/changelog/11381.improvement.rst b/changelog/11381.improvement.rst
new file mode 100644
index 00000000000..74c080cc188
--- /dev/null
+++ b/changelog/11381.improvement.rst
@@ -0,0 +1,17 @@
+The ``type`` parameter of the ``parser.addini`` method now accepts `"int"` and ``"float"`` parameters, facilitating the parsing of configuration values in the configuration file.
+
+Example:
+
+.. code-block:: python
+
+  def pytest_addoption(parser):
+      parser.addini("int_value", type="int", default=2, help="my int value")
+      parser.addini("float_value", type="float", default=4.2, help="my float value")
+
+The `pytest.ini` file:
+
+.. code-block:: ini
+
+  [pytest]
+  int_value = 3
+  float_value = 5.4
diff --git a/changelog/11525.improvement.rst b/changelog/11525.improvement.rst
new file mode 100644
index 00000000000..1935ce59343
--- /dev/null
+++ b/changelog/11525.improvement.rst
@@ -0,0 +1,3 @@
+Fixtures are now clearly represented in the output as a "fixture object", not as a normal function as before, making it easy for beginners to catch mistakes such as referencing a fixture declared in the same module but not requested in the test function.
+
+-- by :user:`the-compiler` and :user:`glyphack`
diff --git a/changelog/11538.feature.rst b/changelog/11538.feature.rst
new file mode 100644
index 00000000000..d6473b8fe73
--- /dev/null
+++ b/changelog/11538.feature.rst
@@ -0,0 +1 @@
+Added :class:`pytest.RaisesGroup` as an equivalent to :func:`pytest.raises` for expecting :exc:`ExceptionGroup`. Also adds :class:`pytest.RaisesExc` which is now the logic behind :func:`pytest.raises` and used as parameter to :class:`pytest.RaisesGroup`. ``RaisesGroup`` includes the ability to specify multiple different expected exceptions, the structure of nested exception groups, and flags for emulating :ref:`except* <except_star>`. See :ref:`assert-matching-exception-groups` and docstrings for more information.
diff --git a/changelog/12008.bugfix.rst b/changelog/12008.bugfix.rst
new file mode 100644
index 00000000000..b9680b89236
--- /dev/null
+++ b/changelog/12008.bugfix.rst
@@ -0,0 +1 @@
+In :pr:`11220`, an unintended change in reordering was introduced by changing the way indices were assigned to direct params. More specifically, before that change, the indices of direct params to metafunc's callspecs were assigned after all parametrizations took place. Now, that change is reverted.
diff --git a/changelog/12017.contrib.rst b/changelog/12017.contrib.rst
new file mode 100644
index 00000000000..ec1861893b3
--- /dev/null
+++ b/changelog/12017.contrib.rst
@@ -0,0 +1,7 @@
+Mixed internal improvements:
+
+* Migrate formatting to f-strings in some tests.
+* Use type-safe constructs in JUnitXML tests.
+* Moved`` MockTiming`` into ``_pytest.timing``.
+
+-- by :user:`RonnyPfannschmidt`
diff --git a/changelog/12081.feature.rst b/changelog/12081.feature.rst
new file mode 100644
index 00000000000..6538fbf30f8
--- /dev/null
+++ b/changelog/12081.feature.rst
@@ -0,0 +1 @@
+Added :fixture:`capteesys` to capture AND pass output to next handler set by ``--capture=``.
diff --git a/changelog/12346.breaking.rst b/changelog/12346.breaking.rst
new file mode 100644
index 00000000000..7013cf734c8
--- /dev/null
+++ b/changelog/12346.breaking.rst
@@ -0,0 +1 @@
+Tests will now fail, instead of raising a warning, if they return any value other than None.
diff --git a/changelog/12426.improvement.rst b/changelog/12426.improvement.rst
new file mode 100644
index 00000000000..0da1f838aea
--- /dev/null
+++ b/changelog/12426.improvement.rst
@@ -0,0 +1 @@
+A warning is now issued when :ref:`pytest.mark.usefixtures ref` is used without specifying any fixtures. Previously, empty usefixtures markers were silently ignored.
diff --git a/changelog/12504.feature.rst b/changelog/12504.feature.rst
new file mode 100644
index 00000000000..d72b97958c2
--- /dev/null
+++ b/changelog/12504.feature.rst
@@ -0,0 +1 @@
+:func:`pytest.mark.xfail` now accepts :class:`pytest.RaisesGroup` for the ``raises`` parameter when you expect an exception group. You can also pass a :class:`pytest.RaisesExc` if you e.g. want to make use of the ``check`` parameter.
diff --git a/changelog/12535.doc.rst b/changelog/12535.doc.rst
new file mode 100644
index 00000000000..d43c1c822ea
--- /dev/null
+++ b/changelog/12535.doc.rst
@@ -0,0 +1,4 @@
+`This
+example`<https://docs.pytest.org/en/latest/example/simple.html#making-test-result-information-available-in-fixtures>
+showed ``print`` statements that do not exactly reflect what the
+different branches actually do.  The fix makes the example more precise.
diff --git a/changelog/12647.contrib.rst b/changelog/12647.contrib.rst
new file mode 100644
index 00000000000..1d7a3181778
--- /dev/null
+++ b/changelog/12647.contrib.rst
@@ -0,0 +1 @@
+Fixed running the test suite with the ``hypothesis`` pytest plugin.
diff --git a/changelog/12707.improvement.rst b/changelog/12707.improvement.rst
new file mode 100644
index 00000000000..4684b6561c8
--- /dev/null
+++ b/changelog/12707.improvement.rst
@@ -0,0 +1 @@
+Exception chains can be navigated when dropped into Pdb in Python 3.13+.
diff --git a/changelog/12713.feature.rst b/changelog/12713.feature.rst
new file mode 100644
index 00000000000..90867b87eae
--- /dev/null
+++ b/changelog/12713.feature.rst
@@ -0,0 +1,3 @@
+New `--force-short-summary` option to force condensed summary output regardless of verbosity level.
+
+This lets users still see condensed summary output of failures for quick reference in log files from job outputs, being especially useful if non-condensed output is very verbose.
diff --git a/changelog/12749.feature.rst b/changelog/12749.feature.rst
new file mode 100644
index 00000000000..c3b7ca5d321
--- /dev/null
+++ b/changelog/12749.feature.rst
@@ -0,0 +1,21 @@
+pytest traditionally collects classes/functions in the test module namespace even if they are imported from another file.
+
+For example:
+
+.. code-block:: python
+
+    # contents of src/domain.py
+    class Testament: ...
+
+
+    # contents of tests/test_testament.py
+    from domain import Testament
+
+
+    def test_testament(): ...
+
+In this scenario with the default options, pytest will collect the class `Testament` from `tests/test_testament.py` because it starts with `Test`, even though in this case it is a production class being imported in the test module namespace.
+
+This behavior can now be prevented by setting the new :confval:`collect_imported_tests` configuration option to ``false``, which will make pytest collect classes/functions from test files **only** if they are defined in that file.
+
+-- by :user:`FreerGit`
diff --git a/changelog/12765.feature.rst b/changelog/12765.feature.rst
new file mode 100644
index 00000000000..193c75621f7
--- /dev/null
+++ b/changelog/12765.feature.rst
@@ -0,0 +1,3 @@
+Thresholds to trigger snippet truncation can now be set with :confval:`truncation_limit_lines` and :confval:`truncation_limit_chars`.
+
+See :ref:`truncation-params` for more information.
diff --git a/changelog/12863.bugfix.rst b/changelog/12863.bugfix.rst
new file mode 100644
index 00000000000..0b1c397a08e
--- /dev/null
+++ b/changelog/12863.bugfix.rst
@@ -0,0 +1 @@
+Fix applying markers, including :ref:`pytest.mark.parametrize <pytest.mark.parametrize ref>` when placed above `@staticmethod` or `@classmethod`.
diff --git a/changelog/12874.breaking.rst b/changelog/12874.breaking.rst
new file mode 100644
index 00000000000..a442586eeb5
--- /dev/null
+++ b/changelog/12874.breaking.rst
@@ -0,0 +1 @@
+We dropped support for Python 3.8 following its end of life (2024-10-07).
diff --git a/changelog/12938.bugfix.rst b/changelog/12938.bugfix.rst
new file mode 100644
index 00000000000..d54d73bdbf5
--- /dev/null
+++ b/changelog/12938.bugfix.rst
@@ -0,0 +1 @@
+Fixed ``--durations-min`` argument not respected if ``-vv`` is used.
diff --git a/changelog/12943.improvement.rst b/changelog/12943.improvement.rst
new file mode 100644
index 00000000000..eb8ac63650a
--- /dev/null
+++ b/changelog/12943.improvement.rst
@@ -0,0 +1 @@
+If a test fails with an exceptiongroup with a single exception, the contained exception will now be displayed in the short test summary info.
diff --git a/changelog/12946.bugfix.rst b/changelog/12946.bugfix.rst
new file mode 100644
index 00000000000..b11da09e7ae
--- /dev/null
+++ b/changelog/12946.bugfix.rst
@@ -0,0 +1 @@
+Fixed missing help for :mod:`pdb` commands wrapped by pytest -- by :user:`adamchainz`.
diff --git a/changelog/12958.improvement.rst b/changelog/12958.improvement.rst
new file mode 100644
index 00000000000..ee8dc8c0710
--- /dev/null
+++ b/changelog/12958.improvement.rst
@@ -0,0 +1,9 @@
+A number of :ref:`unraisable <unraisable>` enhancements:
+
+* Set the unraisable hook as early as possible and unset it as late as possible, to collect the most possible number of unraisable exceptions.
+* Call the garbage collector just before unsetting the unraisable hook, to collect any straggling exceptions.
+* Collect multiple unraisable exceptions per test phase.
+* Report the :mod:`tracemalloc` allocation traceback (if available).
+* Avoid using a generator based hook to allow handling :class:`StopIteration` in test failures.
+* Report the unraisable exception as the cause of the :class:`pytest.PytestUnraisableExceptionWarning` exception if raised.
+* Compute the ``repr`` of the unraisable object in the unraisable hook so you get the latest information if available, and should help with resurrection of the object.
diff --git a/changelog/12960.breaking.rst b/changelog/12960.breaking.rst
new file mode 100644
index 00000000000..3ab87e6fe23
--- /dev/null
+++ b/changelog/12960.breaking.rst
@@ -0,0 +1,3 @@
+Test functions containing a yield now cause an explicit error. They have not been run since pytest 4.0, and were previously marked as an expected failure and deprecation warning.
+
+See :ref:`the docs <yield tests deprecated>` for more information.
diff --git a/changelog/12981.bugfix.rst b/changelog/12981.bugfix.rst
new file mode 100644
index 00000000000..5fc8e29656f
--- /dev/null
+++ b/changelog/12981.bugfix.rst
@@ -0,0 +1 @@
+Prevent exceptions in :func:`pytest.Config.add_cleanup` callbacks preventing further cleanups.
diff --git a/changelog/13010.improvement.rst b/changelog/13010.improvement.rst
new file mode 100644
index 00000000000..d6b814f090e
--- /dev/null
+++ b/changelog/13010.improvement.rst
@@ -0,0 +1 @@
+:func:`pytest.approx` now can compare collections that contain numbers and non-numbers mixed.
diff --git a/changelog/13016.improvement.rst b/changelog/13016.improvement.rst
new file mode 100644
index 00000000000..634672ab69b
--- /dev/null
+++ b/changelog/13016.improvement.rst
@@ -0,0 +1,8 @@
+A number of :ref:`threadexception <unraisable>` enhancements:
+
+* Set the excepthook as early as possible and unset it as late as possible, to collect the most possible number of unhandled exceptions from threads.
+* Collect multiple thread exceptions per test phase.
+* Report the :mod:`tracemalloc` allocation traceback (if available).
+* Avoid using a generator based hook to allow handling :class:`StopIteration` in test failures.
+* Report the thread exception as the cause of the :class:`pytest.PytestUnhandledThreadExceptionWarning` exception if raised.
+* Extract the ``name`` of the thread object in the excepthook which should help with resurrection of the thread.
diff --git a/changelog/13031.improvement.rst b/changelog/13031.improvement.rst
new file mode 100644
index 00000000000..c6c64c4673a
--- /dev/null
+++ b/changelog/13031.improvement.rst
@@ -0,0 +1 @@
+An empty parameter set as in ``pytest.mark.parametrize([], ids=idfunc)`` will no longer trigger a call to ``idfunc`` with internal objects.
diff --git a/changelog/13047.bugfix.rst b/changelog/13047.bugfix.rst
new file mode 100644
index 00000000000..399e860505c
--- /dev/null
+++ b/changelog/13047.bugfix.rst
@@ -0,0 +1,17 @@
+Restore :func:`pytest.approx` handling of equality checks between `bool` and `numpy.bool_` types.
+
+Comparing `bool` and `numpy.bool_` using :func:`pytest.approx` accidentally changed in version `8.3.4` and `8.3.5` to no longer match:
+
+.. code-block:: pycon
+
+    >>> import numpy as np
+    >>> from pytest import approx
+    >>> [np.True_, np.True_] == pytest.approx([True, True])
+    False
+
+This has now been fixed:
+
+.. code-block:: pycon
+
+    >>> [np.True_, np.True_] == pytest.approx([True, True])
+    True
diff --git a/changelog/13115.improvement.rst b/changelog/13115.improvement.rst
new file mode 100644
index 00000000000..9ac45820917
--- /dev/null
+++ b/changelog/13115.improvement.rst
@@ -0,0 +1,8 @@
+Allows supplying ``ExceptionGroup[Exception]`` and ``BaseExceptionGroup[BaseException]`` to ``pytest.raises`` to keep full typing on :class:`ExceptionInfo <pytest.ExceptionInfo>`:
+
+.. code-block:: python
+
+    with pytest.raises(ExceptionGroup[Exception]) as exc_info:
+        some_function()
+
+Parametrizing with other exception types remains an error - we do not check the types of child exceptions and thus do not permit code that might look like we do.
diff --git a/changelog/13119.bugfix.rst b/changelog/13119.bugfix.rst
new file mode 100644
index 00000000000..b7e56af9bb8
--- /dev/null
+++ b/changelog/13119.bugfix.rst
@@ -0,0 +1 @@
+Improved handling of invalid regex patterns for filter warnings by providing a clear error message.
diff --git a/changelog/13122.improvement.rst b/changelog/13122.improvement.rst
new file mode 100644
index 00000000000..c302713b320
--- /dev/null
+++ b/changelog/13122.improvement.rst
@@ -0,0 +1,15 @@
+The ``--stepwise`` mode received a number of improvements:
+
+* It no longer forgets the last failed test in case pytest is executed later without the flag.
+
+  This enables the following workflow:
+
+  1. Execute pytest with ``--stepwise``, pytest then stops at the first failing test;
+  2. Iteratively update the code and run the test in isolation, without the ``--stepwise`` flag (for example in an IDE), until it is fixed.
+  3. Execute pytest with ``--stepwise`` again and pytest will continue from the previously failed test, and if it passes, continue on to the next tests.
+
+  Previously, at step 3, pytest would start from the beginning, forgetting the previously failed test.
+
+  This change however might cause issues if the ``--stepwise`` mode is used far apart in time, as the state might get stale, so the internal state will be reset automatically in case the test suite changes (for now only the number of tests are considered for this, we might change/improve this on the future).
+
+* New ``--stepwise-reset``/``--sw-reset`` flag, allowing the user to explicitly reset the stepwise state and restart the workflow from the beginning.
diff --git a/changelog/13125.feature.rst b/changelog/13125.feature.rst
new file mode 100644
index 00000000000..0c7d66c1169
--- /dev/null
+++ b/changelog/13125.feature.rst
@@ -0,0 +1 @@
+:confval:`console_output_style` now supports ``times`` to show execution time of each test.
diff --git a/changelog/13175.bugfix.rst b/changelog/13175.bugfix.rst
new file mode 100644
index 00000000000..bdbb72b41e1
--- /dev/null
+++ b/changelog/13175.bugfix.rst
@@ -0,0 +1 @@
+The diff is now also highlighted correctly when comparing two strings.
diff --git a/changelog/13192.feature.1.rst b/changelog/13192.feature.1.rst
new file mode 100644
index 00000000000..71fb06f7d70
--- /dev/null
+++ b/changelog/13192.feature.1.rst
@@ -0,0 +1 @@
+:func:`pytest.raises` will now print a helpful string diff if matching fails and the match parameter has ``^`` and ``$`` and is otherwise escaped.
diff --git a/changelog/13192.feature.2.rst b/changelog/13192.feature.2.rst
new file mode 100644
index 00000000000..0ffa0e1496a
--- /dev/null
+++ b/changelog/13192.feature.2.rst
@@ -0,0 +1 @@
+You can now pass :func:`with pytest.raises(check=fn): <pytest.raises>`, where ``fn`` is a function which takes a raised exception and returns a boolean. The ``raises`` fails if no exception was raised (as usual), passes if an exception is raised and ``fn`` returns ``True`` (as well as ``match`` and the type matching, if specified, which are checked before), and propagates the exception if ``fn`` returns ``False`` (which likely also fails the test).
diff --git a/changelog/13192.feature.rst b/changelog/13192.feature.rst
new file mode 100644
index 00000000000..97f31ce233c
--- /dev/null
+++ b/changelog/13192.feature.rst
@@ -0,0 +1 @@
+:func:`pytest.raises` will now raise a warning when passing an empty string to ``match``, as this will match against any value. Use ``match="^$"`` if you want to check that an exception has no message.
diff --git a/changelog/13218.doc.rst b/changelog/13218.doc.rst
new file mode 100644
index 00000000000..907a817e895
--- /dev/null
+++ b/changelog/13218.doc.rst
@@ -0,0 +1 @@
+Pointed out in the :func:`pytest.approx` documentation that it considers booleans unequal to numeric zero or one.
diff --git a/changelog/13221.doc.rst b/changelog/13221.doc.rst
new file mode 100644
index 00000000000..cfd35f821b4
--- /dev/null
+++ b/changelog/13221.doc.rst
@@ -0,0 +1 @@
+Improved grouping of CLI options in the ``--help`` output.
diff --git a/changelog/13228.feature.rst b/changelog/13228.feature.rst
new file mode 100644
index 00000000000..c5d84182313
--- /dev/null
+++ b/changelog/13228.feature.rst
@@ -0,0 +1,3 @@
+:ref:`hidden-param` can now be used in ``id`` of :func:`pytest.param` or in
+``ids`` of :py:func:`Metafunc.parametrize <pytest.Metafunc.parametrize>`.
+It hides the parameter set from the test name.
diff --git a/changelog/13248.bugfix.rst b/changelog/13248.bugfix.rst
new file mode 100644
index 00000000000..2ebb102fd07
--- /dev/null
+++ b/changelog/13248.bugfix.rst
@@ -0,0 +1,2 @@
+Fixed an issue where passing a ``scope`` in :py:func:`Metafunc.parametrize <pytest.Metafunc.parametrize>` with ``indirect=True``
+could result in other fixtures being unable to depend on the parametrized fixture.
diff --git a/changelog/13253.feature.rst b/changelog/13253.feature.rst
new file mode 100644
index 00000000000..e497c207223
--- /dev/null
+++ b/changelog/13253.feature.rst
@@ -0,0 +1 @@
+New flag: :ref:`--disable-plugin-autoload <disable_plugin_autoload>` which works as an alternative to :envvar:`PYTEST_DISABLE_PLUGIN_AUTOLOAD` when setting environment variables is inconvenient; and allows setting it in config files with :confval:`addopts`.
diff --git a/changelog/13291.bugfix.rst b/changelog/13291.bugfix.rst
new file mode 100644
index 00000000000..03ce06b697a
--- /dev/null
+++ b/changelog/13291.bugfix.rst
@@ -0,0 +1 @@
+Fixed ``repr`` of ``attrs`` objects in assertion failure messages when using ``attrs>=25.2``.
diff --git a/changelog/13317.packaging.rst b/changelog/13317.packaging.rst
new file mode 100644
index 00000000000..94171cb1ef3
--- /dev/null
+++ b/changelog/13317.packaging.rst
@@ -0,0 +1,4 @@
+Specified minimum allowed versions of ``colorama``, ``iniconfig``,
+and ``packaging``; and bumped the minimum allowed version
+of ``exceptiongroup`` for ``python_version<'3.11'`` from a release
+candidate to a full release.
diff --git a/changelog/13345.bugfix.rst b/changelog/13345.bugfix.rst
new file mode 100644
index 00000000000..5010888aa08
--- /dev/null
+++ b/changelog/13345.bugfix.rst
@@ -0,0 +1 @@
+Fix type hints for :attr:`pytest.TestReport.when` and :attr:`pytest.TestReport.location`.
diff --git a/changelog/4112.improvement.rst b/changelog/4112.improvement.rst
new file mode 100644
index 00000000000..426b87ffa19
--- /dev/null
+++ b/changelog/4112.improvement.rst
@@ -0,0 +1 @@
+Using :ref:`pytest.mark.usefixtures <pytest.mark.usefixtures ref>` on :func:`pytest.param` now produces an error instead of silently doing nothing.
diff --git a/changelog/4320.doc.rst b/changelog/4320.doc.rst
deleted file mode 100644
index 70a4a743f36..00000000000
--- a/changelog/4320.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Improved docs for `pytester.copy_example`.
diff --git a/changelog/5105.doc.rst b/changelog/5105.doc.rst
deleted file mode 100644
index 71c1edc9fc4..00000000000
--- a/changelog/5105.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add automatically generated :ref:`plugin-list`. The list is updated on a periodic schedule.
diff --git a/changelog/5196.feature.rst b/changelog/5196.feature.rst
deleted file mode 100644
index 5e6312b482d..00000000000
--- a/changelog/5196.feature.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Tests are now ordered by definition order in more cases.
-
-In a class hierarchy, tests from base classes are now consistently ordered before tests defined on their subclasses (reverse MRO order).
diff --git a/changelog/6649.doc.rst b/changelog/6649.doc.rst
new file mode 100644
index 00000000000..cf5bb781b87
--- /dev/null
+++ b/changelog/6649.doc.rst
@@ -0,0 +1 @@
+Added :class:`~pytest.TerminalReporter` to the :ref:`api-reference` documentation page.
diff --git a/changelog/6649.misc.rst b/changelog/6649.misc.rst
new file mode 100644
index 00000000000..cec8c3f4506
--- /dev/null
+++ b/changelog/6649.misc.rst
@@ -0,0 +1 @@
+Added :class:`~pytest.TerminalReporter` to the public pytest API, as it is part of the signature of the :hook:`pytest_terminal_summary` hook.
diff --git a/changelog/6985.improvement.rst b/changelog/6985.improvement.rst
new file mode 100644
index 00000000000..34ee8edc77d
--- /dev/null
+++ b/changelog/6985.improvement.rst
@@ -0,0 +1,21 @@
+Improved :func:`pytest.approx` to enhance the readability of value ranges and tolerances between 0.001 and 1000.
+  * The `repr` method now provides clearer output for values within those ranges, making it easier to interpret the results.
+  * Previously, the output for those ranges of values and tolerances was displayed in scientific notation (e.g., `42 ± 1.0e+00`). The updated method now presents the tolerance as a decimal for better readability (e.g., `42 ± 1`).
+
+    Example:
+
+    **Previous Output:**
+
+    .. code-block:: console
+
+        >>> pytest.approx(42, abs=1)
+        42 ± 1.0e+00
+
+    **Current Output:**
+
+    .. code-block:: console
+
+        >>> pytest.approx(42, abs=1)
+        42 ± 1
+
+  -- by :user:`fazeelghafoor`
diff --git a/changelog/7124.bugfix.rst b/changelog/7124.bugfix.rst
deleted file mode 100644
index 191925d7a24..00000000000
--- a/changelog/7124.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed an issue where ``__main__.py`` would raise an ``ImportError`` when ``--doctest-modules`` was provided.
diff --git a/changelog/7132.feature.rst b/changelog/7132.feature.rst
deleted file mode 100644
index 9fb735ee153..00000000000
--- a/changelog/7132.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-Added two environment variables :envvar:`PYTEST_THEME` and :envvar:`PYTEST_THEME_MODE` to let the users customize the pygments theme used.
diff --git a/changelog/7259.breaking.rst b/changelog/7259.breaking.rst
deleted file mode 100644
index 48ecbcbb730..00000000000
--- a/changelog/7259.breaking.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-The :ref:`Node.reportinfo() <non-python tests>` function first return value type has been expanded from `py.path.local | str` to `os.PathLike[str] | str`.
-
-Most plugins which refer to `reportinfo()` only define it as part of a custom :class:`pytest.Item` implementation.
-Since `py.path.local` is a `os.PathLike[str]`, these plugins are unaffacted.
-
-Plugins and users which call `reportinfo()`, use the first return value and interact with it as a `py.path.local`, would need to adjust by calling `py.path.local(fspath)`.
-Although preferably, avoid the legacy `py.path.local` and use `pathlib.Path`, or use `item.location` or `item.path`, instead.
-
-Note: pytest was not able to provide a deprecation period for this change.
diff --git a/changelog/7259.deprecation.rst b/changelog/7259.deprecation.rst
deleted file mode 100644
index ae857c0d826..00000000000
--- a/changelog/7259.deprecation.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-``py.path.local`` arguments for hooks have been deprecated. See :ref:`the deprecation note <legacy-path-hooks-deprecated>` for full details.
-
-``py.path.local`` arguments to Node constructors have been deprecated. See :ref:`the deprecation note <node-ctor-fspath-deprecation>` for full details.
-
-.. note::
-    The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
-    new attribute being ``path``) is **the opposite** of the situation for hooks
-    (the old argument being ``path``).
-
-    This is an unfortunate artifact due to historical reasons, which should be
-    resolved in future versions as we slowly get rid of the :pypi:`py`
-    dependency (see :issue:`9283` for a longer discussion).
diff --git a/changelog/7259.feature.rst b/changelog/7259.feature.rst
deleted file mode 100644
index dd03b48969d..00000000000
--- a/changelog/7259.feature.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Added :meth:`cache.mkdir() <pytest.Cache.mkdir>`, which is similar to the existing :meth:`cache.makedir() <pytest.Cache.makedir>`,
-but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``.
-
-Added a ``paths`` type to :meth:`parser.addini() <pytest.Parser.addini>`,
-as in ``parser.addini("mypaths", "my paths", type="paths")``,
-which is similar to the existing ``pathlist``,
-but returns a list of :class:`pathlib.Path` instead of legacy ``py.path.local``.
diff --git a/changelog/7469.deprecation.rst b/changelog/7469.deprecation.rst
deleted file mode 100644
index ea8c7c0b4f9..00000000000
--- a/changelog/7469.deprecation.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-Directly constructing the following classes is now deprecated:
-
-- ``_pytest.mark.structures.Mark``
-- ``_pytest.mark.structures.MarkDecorator``
-- ``_pytest.mark.structures.MarkGenerator``
-- ``_pytest.python.Metafunc``
-- ``_pytest.runner.CallInfo``
-- ``_pytest._code.ExceptionInfo``
-- ``_pytest.config.argparsing.Parser``
-- ``_pytest.config.argparsing.OptionGroup``
-- ``_pytest.pytester.HookRecorder``
-
-These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8.
diff --git a/changelog/7469.feature.rst b/changelog/7469.feature.rst
deleted file mode 100644
index 4694e97e9d9..00000000000
--- a/changelog/7469.feature.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-The types of objects used in pytest's API are now exported so they may be used in type annotations.
-
-The newly-exported types are:
-
-- ``pytest.Config`` for :class:`Config <pytest.Config>`.
-- ``pytest.Mark`` for :class:`marks <pytest.Mark>`.
-- ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`.
-- ``pytest.MarkGenerator`` for the :class:`pytest.mark <pytest.MarkGenerator>` singleton.
-- ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the :func:`pytest_generate_tests <pytest.hookspec.pytest_generate_tests>` hook.
-- ``pytest.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks.
-- ``pytest.PytestPluginManager`` for :class:`PytestPluginManager <pytest.PytestPluginManager>`.
-- ``pytest.ExceptionInfo`` for the :class:`ExceptionInfo <pytest.ExceptionInfo>` type returned from :func:`pytest.raises` and passed to various hooks.
-- ``pytest.Parser`` for the :class:`Parser <pytest.Parser>` type passed to the :func:`pytest_addoption <pytest.hookspec.pytest_addoption>` hook.
-- ``pytest.OptionGroup`` for the :class:`OptionGroup <pytest.OptionGroup>` type returned from the :func:`parser.addgroup <pytest.Parser.getgroup>` method.
-- ``pytest.HookRecorder`` for the :class:`HookRecorder <pytest.HookRecorder>` type returned from :class:`~pytest.Pytester`.
-- ``pytest.RecordedHookCall`` for the :class:`RecordedHookCall <pytest.HookRecorder>` type returned from :class:`~pytest.HookRecorder`.
-- ``pytest.RunResult`` for the :class:`RunResult <pytest.RunResult>` type returned from :class:`~pytest.Pytester`.
-- ``pytest.LineMatcher`` for the :class:`LineMatcher <pytest.RunResult>` type used in :class:`~pytest.RunResult` and others.
-- ``pytest.TestReport`` for the :class:`TestReport <pytest.TestReport>` type used in various hooks.
-- ``pytest.CollectReport`` for the :class:`CollectReport <pytest.CollectReport>` type used in various hooks.
-
-Constructing most of them directly is not supported; they are only meant for use in type annotations.
-Doing so will emit a deprecation warning, and may become a hard-error in pytest 8.0.
-
-Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy.
diff --git a/changelog/7480.improvement.rst b/changelog/7480.improvement.rst
deleted file mode 100644
index 296e6e7fb54..00000000000
--- a/changelog/7480.improvement.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-A deprecation scheduled to be removed in a major version X (e.g. pytest 7, 8, 9, ...) now uses warning category `PytestRemovedInXWarning`,
-a subclass of :class:`~pytest.PytestDeprecationWarning`,
-instead of :class:`PytestDeprecationWarning` directly.
-
-See :ref:`backwards-compatibility` for more details.
diff --git a/changelog/7683.improvement.rst b/changelog/7683.improvement.rst
new file mode 100644
index 00000000000..311abe4df93
--- /dev/null
+++ b/changelog/7683.improvement.rst
@@ -0,0 +1 @@
+The formerly optional ``pygments`` dependency is now required, causing output always to be source-highlighted (unless disabled via the ``--code-highlight=no`` CLI option).
diff --git a/changelog/7856.feature.rst b/changelog/7856.feature.rst
deleted file mode 100644
index 22ed4c83bc3..00000000000
--- a/changelog/7856.feature.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:ref:`--import-mode=importlib <import-modes>` now works with features that
-depend on modules being on :py:data:`sys.modules`, such as :mod:`pickle` and :mod:`dataclasses`.
diff --git a/changelog/7864.improvement.rst b/changelog/7864.improvement.rst
deleted file mode 100644
index 195632346fe..00000000000
--- a/changelog/7864.improvement.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Improved error messages when parsing warning filters.
-
-Previously pytest would show an internal traceback, which besides being ugly sometimes would hide the cause
-of the problem (for example an ``ImportError`` while importing a specific warning type).
diff --git a/changelog/8061.bugfix.rst b/changelog/8061.bugfix.rst
deleted file mode 100644
index 5686af66321..00000000000
--- a/changelog/8061.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed failing ``staticmethod`` test cases if they are inherited from a parent test class.
diff --git a/changelog/8133.trivial.rst b/changelog/8133.trivial.rst
deleted file mode 100644
index 7ceaae59765..00000000000
--- a/changelog/8133.trivial.rst
+++ /dev/null
@@ -1 +0,0 @@
-Migrate to ``setuptools_scm`` 6.x to use ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST`` for more robust release tooling.
diff --git a/changelog/8144.feature.rst b/changelog/8144.feature.rst
deleted file mode 100644
index fe576eeffa8..00000000000
--- a/changelog/8144.feature.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument:
-
-- :func:`pytest_ignore_collect <_pytest.hookspec.pytest_ignore_collect>` - The ``collection_path`` parameter (equivalent to existing ``path`` parameter).
-- :func:`pytest_collect_file <_pytest.hookspec.pytest_collect_file>` - The ``file_path`` parameter (equivalent to existing ``path`` parameter).
-- :func:`pytest_pycollect_makemodule <_pytest.hookspec.pytest_pycollect_makemodule>` - The ``module_path`` parameter (equivalent to existing ``path`` parameter).
-- :func:`pytest_report_header <_pytest.hookspec.pytest_report_header>` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
-- :func:`pytest_report_collectionfinish <_pytest.hookspec.pytest_report_collectionfinish>` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
-
-.. note::
-    The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
-    new attribute being ``path``) is **the opposite** of the situation for hooks
-    (the old argument being ``path``).
-
-    This is an unfortunate artifact due to historical reasons, which should be
-    resolved in future versions as we slowly get rid of the :pypi:`py`
-    dependency (see :issue:`9283` for a longer discussion).
diff --git a/changelog/8174.trivial.rst b/changelog/8174.trivial.rst
deleted file mode 100644
index 7649764618f..00000000000
--- a/changelog/8174.trivial.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-The following changes have been made to internal pytest types/functions:
-
-- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``.
-- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``.
-- The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``.
-- The ``_pytest.python.path_matches_patterns()`` function takes ``Path`` instead of ``py.path.local``.
diff --git a/changelog/8192.bugfix.rst b/changelog/8192.bugfix.rst
deleted file mode 100644
index 8920b200a21..00000000000
--- a/changelog/8192.bugfix.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-``testdir.makefile`` now silently accepts values which don't start with ``.`` to maintain backward compatibility with older pytest versions.
-
-``pytester.makefile`` now issues a clearer error if the ``.`` is missing in the ``ext`` argument.
diff --git a/changelog/8242.deprecation.rst b/changelog/8242.deprecation.rst
deleted file mode 100644
index 3875c5867e3..00000000000
--- a/changelog/8242.deprecation.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Raising :class:`unittest.SkipTest` to skip collection of tests during the
-pytest collection phase is deprecated. Use :func:`pytest.skip` instead.
-
-Note: This deprecation only relates to using :class:`unittest.SkipTest` during test
-collection. You are probably not doing that. Ordinary usage of
-:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` /
-:func:`unittest.skip` in unittest test cases is fully supported.
diff --git a/changelog/8246.breaking.rst b/changelog/8246.breaking.rst
deleted file mode 100644
index ba9e5c8beea..00000000000
--- a/changelog/8246.breaking.rst
+++ /dev/null
@@ -1 +0,0 @@
-``--version`` now writes version information to ``stdout`` rather than ``stderr``.
diff --git a/changelog/8248.trivial.rst b/changelog/8248.trivial.rst
deleted file mode 100644
index 3044071fa81..00000000000
--- a/changelog/8248.trivial.rst
+++ /dev/null
@@ -1 +0,0 @@
-Internal Restructure: let ``python.PyObjMixin`` inherit from ``nodes.Node`` to carry over typing information.
diff --git a/changelog/8251.feature.rst b/changelog/8251.feature.rst
deleted file mode 100644
index fe157cc98b7..00000000000
--- a/changelog/8251.feature.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-Implement ``Node.path`` as a ``pathlib.Path``. Both the old ``fspath`` and this new attribute gets set no matter whether ``path`` or ``fspath`` (deprecated) is passed to the constructor. It is a replacement for the ``fspath`` attribute (which represents the same path as ``py.path.local``). While ``fspath`` is not deprecated yet
-due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo`, we expect to deprecate it in a future release.
-
-.. note::
-    The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
-    new attribute being ``path``) is **the opposite** of the situation for hooks
-    (the old argument being ``path``).
-
-    This is an unfortunate artifact due to historical reasons, which should be
-    resolved in future versions as we slowly get rid of the :pypi:`py`
-    dependency (see :issue:`9283` for a longer discussion).
diff --git a/changelog/8258.bugfix.rst b/changelog/8258.bugfix.rst
deleted file mode 100644
index 6518ec0b738..00000000000
--- a/changelog/8258.bugfix.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Fixed issue where pytest's ``faulthandler`` support would not dump traceback on crashes
-if the :mod:`faulthandler` module was already enabled during pytest startup (using
-``python -X dev -m pytest`` for example).
diff --git a/changelog/8315.deprecation.rst b/changelog/8315.deprecation.rst
deleted file mode 100644
index b204dcedd9b..00000000000
--- a/changelog/8315.deprecation.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
-scheduled for removal in pytest 8 (deprecated since pytest 2.4.0):
-
-- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead.
-- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.
diff --git a/changelog/8317.bugfix.rst b/changelog/8317.bugfix.rst
deleted file mode 100644
index 7312880a11f..00000000000
--- a/changelog/8317.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed an issue where illegal directory characters derived from ``getpass.getuser()`` raised an ``OSError``.
diff --git a/changelog/8335.improvement.rst b/changelog/8335.improvement.rst
deleted file mode 100644
index f6c0e3343f0..00000000000
--- a/changelog/8335.improvement.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-Improved :func:`pytest.approx` assertion messages for sequences of numbers.
-
-The assertion messages now dumps a table with the index and the error of each diff.
-Example::
-
-    >       assert [1, 2, 3, 4] == pytest.approx([1, 3, 3, 5])
-    E       assert comparison failed for 2 values:
-    E         Index | Obtained | Expected
-    E         1     | 2        | 3 +- 3.0e-06
-    E         3     | 4        | 5 +- 5.0e-06
diff --git a/changelog/8337.doc.rst b/changelog/8337.doc.rst
deleted file mode 100644
index f2483a6b481..00000000000
--- a/changelog/8337.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Recommend `numpy.testing <https://numpy.org/doc/stable/reference/routines.testing.html>`__ module on :func:`pytest.approx` documentation.
diff --git a/changelog/8367.bugfix.rst b/changelog/8367.bugfix.rst
deleted file mode 100644
index f4b03670108..00000000000
--- a/changelog/8367.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix ``Class.from_parent`` so it forwards extra keyword arguments to the constructor.
diff --git a/changelog/8377.bugfix.rst b/changelog/8377.bugfix.rst
deleted file mode 100644
index 3df54a949af..00000000000
--- a/changelog/8377.bugfix.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-The test selection options ``pytest -k`` and ``pytest -m`` now support matching
-names containing forward slash (``/``) characters.
diff --git a/changelog/8384.bugfix.rst b/changelog/8384.bugfix.rst
deleted file mode 100644
index 3b70987490e..00000000000
--- a/changelog/8384.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-The ``@pytest.mark.skip`` decorator now correctly handles its arguments. When the ``reason`` argument is accidentally given both positional and as a keyword (e.g. because it was confused with ``skipif``), a ``TypeError`` now occurs. Before, such tests were silently skipped, and the positional argument ignored. Additionally, ``reason`` is now documented correctly as positional or keyword (rather than keyword-only).
diff --git a/changelog/8394.bugfix.rst b/changelog/8394.bugfix.rst
deleted file mode 100644
index a0fb5bb71fd..00000000000
--- a/changelog/8394.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Use private names for internal fixtures that handle classic setup/teardown so that they don't show up with the default ``--fixtures`` invocation (but they still show up with ``--fixtures -v``).
diff --git a/changelog/8403.improvement.rst b/changelog/8403.improvement.rst
deleted file mode 100644
index ec392245f67..00000000000
--- a/changelog/8403.improvement.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-By default, pytest will truncate long strings in assert errors so they don't clutter the output too much,
-currently at ``240`` characters by default.
-
-However, in some cases the longer output helps, or is even crucial, to diagnose a failure. Using ``-v`` will
-now increase the truncation threshold to ``2400`` characters, and ``-vv`` or higher will disable truncation entirely.
diff --git a/changelog/8421.feature.rst b/changelog/8421.feature.rst
deleted file mode 100644
index c729ca3950a..00000000000
--- a/changelog/8421.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-:func:`pytest.approx` now works on :class:`~decimal.Decimal` within mappings/dicts and sequences/lists.
diff --git a/changelog/8432.trivial.rst b/changelog/8432.trivial.rst
deleted file mode 100644
index af4c7a22617..00000000000
--- a/changelog/8432.trivial.rst
+++ /dev/null
@@ -1 +0,0 @@
-Improve error message when :func:`pytest.skip` is used at module level without passing `allow_module_level=True`.
diff --git a/changelog/8447.deprecation.rst b/changelog/8447.deprecation.rst
deleted file mode 100644
index 8386e3b7480..00000000000
--- a/changelog/8447.deprecation.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-Defining a custom pytest node type which is both an :class:`pytest.Item <Item>` and a :class:`pytest.Collector <Collector>` (e.g. :class:`pytest.File <File>`) now issues a warning.
-It was never sanely supported and triggers hard to debug errors.
-
-See :ref:`the deprecation note <diamond-inheritance-deprecated>` for full details.
diff --git a/changelog/8456.bugfix.rst b/changelog/8456.bugfix.rst
deleted file mode 100644
index da9370b7b1b..00000000000
--- a/changelog/8456.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-The :confval:`required_plugins` config option now works correctly when pre-releases of plugins are installed, rather than falsely claiming that those plugins aren't installed at all.
diff --git a/changelog/8464.bugfix.rst b/changelog/8464.bugfix.rst
deleted file mode 100644
index 017c4c078fb..00000000000
--- a/changelog/8464.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-``-c <config file>`` now also properly defines ``rootdir`` as the directory that contains ``<config file>``.
diff --git a/changelog/8503.bugfix.rst b/changelog/8503.bugfix.rst
deleted file mode 100644
index 26f660bbf3f..00000000000
--- a/changelog/8503.bugfix.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-:meth:`pytest.MonkeyPatch.syspath_prepend` no longer fails when
-``setuptools`` is not installed.
-It now only calls :func:`pkg_resources.fixup_namespace_packages` if
-``pkg_resources`` was previously imported, because it is not needed otherwise.
diff --git a/changelog/8509.improvement.rst b/changelog/8509.improvement.rst
deleted file mode 100644
index 982888c1fea..00000000000
--- a/changelog/8509.improvement.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Fixed issue where :meth:`unittest.TestCase.setUpClass` is not called when a test has `/` in its name since pytest 6.2.0.
-
-This refers to the path part in pytest node IDs, e.g. ``TestClass::test_it`` in the node ID ``tests/test_file.py::TestClass::test_it``.
-
-Now, instead of assuming that the test name does not contain ``/``, it is assumed that test path does not contain ``::``. We plan to hopefully make both of these work in the future.
diff --git a/changelog/8548.bugfix.rst b/changelog/8548.bugfix.rst
deleted file mode 100644
index 9201169fc0b..00000000000
--- a/changelog/8548.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Introduce fix to handle precision width in ``log-cli-format`` in turn to fix output coloring for certain formats.
diff --git a/changelog/8592.deprecation.rst b/changelog/8592.deprecation.rst
deleted file mode 100644
index 733ad3586e5..00000000000
--- a/changelog/8592.deprecation.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-:func:`pytest_cmdline_preparse <_pytest.hookspec.pytest_cmdline_preparse>` has been officially deprecated.  It will be removed in a future release.  Use :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load_initial_conftests>` instead.
-
-See :ref:`the deprecation note <cmdline-preparse-deprecated>` for full details.
diff --git a/changelog/8606.feature.rst b/changelog/8606.feature.rst
deleted file mode 100644
index d918ecd1ec1..00000000000
--- a/changelog/8606.feature.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-pytest invocations with ``--fixtures-per-test`` and ``--fixtures`` have been enriched with:
-
-- Fixture location path printed with the fixture name.
-- First section of the fixture's docstring printed under the fixture name.
-- Whole of fixture's docstring printed under the fixture name using ``--verbose`` option.
diff --git a/changelog/8612.doc.rst b/changelog/8612.doc.rst
new file mode 100644
index 00000000000..6ab4102ace4
--- /dev/null
+++ b/changelog/8612.doc.rst
@@ -0,0 +1,5 @@
+Add a recipe for handling abstract test classes in the documentation.
+
+A new example has been added to the documentation to demonstrate how to use a mixin class to handle abstract
+test classes without manually setting the ``__test__`` attribute for subclasses.
+This ensures that subclasses of abstract test classes are automatically collected by pytest.
diff --git a/changelog/8645.deprecation.rst b/changelog/8645.deprecation.rst
deleted file mode 100644
index 722a5d764de..00000000000
--- a/changelog/8645.deprecation.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-:func:`pytest.warns(None) <pytest.warns>` is now deprecated because many people used
-it to mean "this code does not emit warnings", but it actually had the effect of
-checking that the code emits at least one warning of any type - like ``pytest.warns()``
-or ``pytest.warns(Warning)``.
diff --git a/changelog/8655.doc.rst b/changelog/8655.doc.rst
deleted file mode 100644
index 65051a74319..00000000000
--- a/changelog/8655.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Help text for ``--pdbcls`` more accurately reflects the option's behavior.
diff --git a/changelog/8733.breaking.rst b/changelog/8733.breaking.rst
deleted file mode 100644
index fa2a43ac655..00000000000
--- a/changelog/8733.breaking.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-Drop a workaround for `pyreadline <https://github.com/pyreadline/pyreadline>`__ that made it work with ``--pdb``.
-
-The workaround was introduced in `#1281 <https://github.com/pytest-dev/pytest/pull/1281>`__ in 2015, however since then
-`pyreadline seems to have gone unmaintained <https://github.com/pyreadline/pyreadline/issues/58>`__, is `generating
-warnings <https://github.com/pytest-dev/pytest/issues/8847>`__, and will stop working on Python 3.10.
diff --git a/changelog/8761.feature.rst b/changelog/8761.feature.rst
deleted file mode 100644
index 88288c81002..00000000000
--- a/changelog/8761.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-New :ref:`version-tuple` attribute, which makes it simpler for users to do something depending on the pytest version (such as declaring hooks which are introduced in later versions).
diff --git a/changelog/8789.feature.rst b/changelog/8789.feature.rst
deleted file mode 100644
index 23215c97ef2..00000000000
--- a/changelog/8789.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-Switch TOML parser from ``toml`` to ``tomli`` for TOML v1.0.0 support in ``pyproject.toml``.
diff --git a/changelog/8796.bugfix.rst b/changelog/8796.bugfix.rst
deleted file mode 100644
index 1e83f4e2613..00000000000
--- a/changelog/8796.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed internal error when skipping doctests.
diff --git a/changelog/8803.improvement.rst b/changelog/8803.improvement.rst
deleted file mode 100644
index 1e4db257dfc..00000000000
--- a/changelog/8803.improvement.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-It is now possible to add colors to custom log levels on cli log.
-
-By using :func:`add_color_level <_pytest.logging.add_color_level>` from a ``pytest_configure`` hook, colors can be added::
-
-    logging_plugin = config.pluginmanager.get_plugin('logging-plugin')
-    logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, 'cyan')
-    logging_plugin.log_cli_handler.formatter.add_color_level(logging.SPAM, 'blue')
-
-See :ref:`log_colors` for more information.
diff --git a/changelog/8818.trivial.rst b/changelog/8818.trivial.rst
deleted file mode 100644
index e8c7a24c61b..00000000000
--- a/changelog/8818.trivial.rst
+++ /dev/null
@@ -1 +0,0 @@
-Ensure ``regendoc`` opts out of ``TOX_ENV`` cachedir selection to ensure independent example test runs.
diff --git a/changelog/8822.improvement.rst b/changelog/8822.improvement.rst
deleted file mode 100644
index a89bcd6baa7..00000000000
--- a/changelog/8822.improvement.rst
+++ /dev/null
@@ -1 +0,0 @@
-When showing fixture paths in `--fixtures` or `--fixtures-by-test`, fixtures coming from pytest itself now display an elided path, rather than the full path to the file in the `site-packages` directory.
diff --git a/changelog/8898.improvement.rst b/changelog/8898.improvement.rst
deleted file mode 100644
index d725157cfc6..00000000000
--- a/changelog/8898.improvement.rst
+++ /dev/null
@@ -1 +0,0 @@
-Complex numbers are now treated like floats and integers when generating parameterization IDs.
diff --git a/changelog/8913.trivial.rst b/changelog/8913.trivial.rst
deleted file mode 100644
index 0d971c475a5..00000000000
--- a/changelog/8913.trivial.rst
+++ /dev/null
@@ -1 +0,0 @@
-The private ``CallSpec2._arg2scopenum`` attribute has been removed after an internal refactoring.
diff --git a/changelog/8920.feature.rst b/changelog/8920.feature.rst
deleted file mode 100644
index 05bdab6da75..00000000000
--- a/changelog/8920.feature.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Added :class:`pytest.Stash`, a facility for plugins to store their data on :class:`~pytest.Config` and :class:`~_pytest.nodes.Node`\s in a type-safe and conflict-free manner.
-See :ref:`plugin-stash` for details.
diff --git a/changelog/8948.deprecation.rst b/changelog/8948.deprecation.rst
deleted file mode 100644
index a39a92bc70e..00000000000
--- a/changelog/8948.deprecation.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-:func:`pytest.skip(msg=...) <pytest.skip>`, :func:`pytest.fail(msg=...) <pytest.fail>` and :func:`pytest.exit(msg=...) <pytest.exit>`
-signatures now accept a ``reason`` argument instead of ``msg``.  Using ``msg`` still works, but is deprecated and will be removed in a future release.
-
-This was changed for consistency with :func:`pytest.mark.skip <pytest.mark.skip>` and  :func:`pytest.mark.xfail <pytest.mark.xfail>` which both accept
-``reason`` as an argument.
diff --git a/changelog/8953.feature.rst b/changelog/8953.feature.rst
deleted file mode 100644
index aa60fa19ca2..00000000000
--- a/changelog/8953.feature.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a
-``warnings`` argument to assert the total number of warnings captured.
diff --git a/changelog/8954.feature.rst b/changelog/8954.feature.rst
deleted file mode 100644
index 7edb430075e..00000000000
--- a/changelog/8954.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-``--debug`` flag now accepts a :class:`str` file to route debug logs into, remains defaulted to `pytestdebug.log`.
diff --git a/changelog/8967.trivial.rst b/changelog/8967.trivial.rst
deleted file mode 100644
index d5f773241f5..00000000000
--- a/changelog/8967.trivial.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:func:`pytest_assertion_pass <_pytest.hookspec.pytest_assertion_pass>` is no longer considered experimental and
-future changes to it will be considered more carefully.
diff --git a/changelog/8983.bugfix.rst b/changelog/8983.bugfix.rst
deleted file mode 100644
index 403d421d6bd..00000000000
--- a/changelog/8983.bugfix.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-The test selection options ``pytest -k`` and ``pytest -m`` now support matching names containing backslash (`\\`) characters.
-Backslashes are treated literally, not as escape characters (the values being matched against are already escaped).
diff --git a/changelog/8990.bugfix.rst b/changelog/8990.bugfix.rst
deleted file mode 100644
index ab63eb9a75e..00000000000
--- a/changelog/8990.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix `pytest -vv` crashing with an internal exception `AttributeError: 'str' object has no attribute 'relative_to'` in some cases.
diff --git a/changelog/9023.feature.rst b/changelog/9023.feature.rst
deleted file mode 100644
index 86a819a84e0..00000000000
--- a/changelog/9023.feature.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-
-Full diffs are now always shown for equality assertions of iterables when
-`CI` or ``BUILD_NUMBER`` is found in the environment, even when ``-v`` isn't
-used.
diff --git a/changelog/9037.bugfix.rst b/changelog/9037.bugfix.rst
new file mode 100644
index 00000000000..5367452337e
--- /dev/null
+++ b/changelog/9037.bugfix.rst
@@ -0,0 +1 @@
+Honor :confval:`disable_test_id_escaping_and_forfeit_all_rights_to_community_support` when escaping ids in parametrized tests.
diff --git a/changelog/9061.breaking.rst b/changelog/9061.breaking.rst
deleted file mode 100644
index bf639e13214..00000000000
--- a/changelog/9061.breaking.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-Using :func:`pytest.approx` in a boolean context now raises an error hinting at the proper usage.
-
-It is apparently common for users to mistakenly use ``pytest.approx`` like this:
-
-.. code-block:: python
-
-    assert pytest.approx(actual, expected)
-
-While the correct usage is:
-
-.. code-block:: python
-
-    assert actual == pytest.approx(expected)
-
-The new error message helps catch those mistakes.
diff --git a/changelog/9062.improvement.rst b/changelog/9062.improvement.rst
deleted file mode 100644
index bfc7cf00a46..00000000000
--- a/changelog/9062.improvement.rst
+++ /dev/null
@@ -1 +0,0 @@
-``--stepwise-skip`` now implicitly enables ``--stepwise`` and can be used on its own.
diff --git a/changelog/9077.bugfix.rst b/changelog/9077.bugfix.rst
deleted file mode 100644
index fcee5d385df..00000000000
--- a/changelog/9077.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed confusing error message when ``request.fspath`` / ``request.path`` was accessed from a session-scoped fixture.
diff --git a/changelog/9113.feature.rst b/changelog/9113.feature.rst
deleted file mode 100644
index f16e6ea63fd..00000000000
--- a/changelog/9113.feature.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-:class:`RunResult <_pytest.pytester.RunResult>` method :meth:`assert_outcomes <_pytest.pytester.RunResult.assert_outcomes>` now accepts a
-``deselected`` argument to assert the total number of deselected tests.
diff --git a/changelog/9114.feature.rst b/changelog/9114.feature.rst
deleted file mode 100644
index 0a576c3b735..00000000000
--- a/changelog/9114.feature.rst
+++ /dev/null
@@ -1 +0,0 @@
-Added :confval:`pythonpath` setting that adds listed paths to :data:`sys.path` for the duration of the test session. If you currently use the pytest-pythonpath or pytest-srcpaths plugins, you should be able to replace them with built-in `pythonpath` setting.
diff --git a/changelog/9131.bugfix.rst b/changelog/9131.bugfix.rst
deleted file mode 100644
index 837bb596db5..00000000000
--- a/changelog/9131.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed the URL used by ``--pastebin`` to use `bpa.st <http://bpa.st>`__.
diff --git a/changelog/9163.bugfix.rst b/changelog/9163.bugfix.rst
deleted file mode 100644
index fb559d10fe8..00000000000
--- a/changelog/9163.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-The end line number and end column offset are now properly set for rewritten assert statements.
diff --git a/changelog/9169.bugfix.rst b/changelog/9169.bugfix.rst
deleted file mode 100644
index 83fce0a3893..00000000000
--- a/changelog/9169.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Support for the ``files`` API from ``importlib.resources`` within rewritten files.
diff --git a/changelog/9202.trivial.rst b/changelog/9202.trivial.rst
deleted file mode 100644
index 916d75074b9..00000000000
--- a/changelog/9202.trivial.rst
+++ /dev/null
@@ -1 +0,0 @@
-Add github action to upload coverage report to codecov instead of bash uploader.
diff --git a/changelog/9205.improvement.rst b/changelog/9205.improvement.rst
deleted file mode 100644
index edfc3008965..00000000000
--- a/changelog/9205.improvement.rst
+++ /dev/null
@@ -1 +0,0 @@
-:meth:`pytest.Cache.set` now preserves key order when saving dicts.
diff --git a/changelog/9210.doc.rst b/changelog/9210.doc.rst
deleted file mode 100644
index 02a850c08c4..00000000000
--- a/changelog/9210.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Remove incorrect docs about ``confcutdir`` being a configuration option: it can only be set through the ``--confcutdir`` command-line option.
diff --git a/changelog/9225.trivial.rst b/changelog/9225.trivial.rst
deleted file mode 100644
index 979f7fd0ce3..00000000000
--- a/changelog/9225.trivial.rst
+++ /dev/null
@@ -1 +0,0 @@
-Changed the command used to create sdist and wheel artifacts: using the build package instead of setup.py.
diff --git a/changelog/9242.doc.rst b/changelog/9242.doc.rst
deleted file mode 100644
index ef2afc744bf..00000000000
--- a/changelog/9242.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Upgrade readthedocs configuration to use a `newer Ubuntu version <https://blog.readthedocs.com/new-build-specification/>`__` with better unicode support for PDF docs.
diff --git a/changelog/9272.bugfix.rst b/changelog/9272.bugfix.rst
deleted file mode 100644
index 0f242d40891..00000000000
--- a/changelog/9272.bugfix.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-The nose compatibility module-level fixtures `setup()` and `teardown()` are now only called once per module, instead of for each test function.
-They are now called even if object-level `setup`/`teardown` is defined.
diff --git a/changelog/9277.breaking.rst b/changelog/9277.breaking.rst
deleted file mode 100644
index 0296dcd7c99..00000000000
--- a/changelog/9277.breaking.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-The ``pytest.Instance`` collector type has been removed.
-Importing ``pytest.Instance`` or ``_pytest.python.Instance`` returns a dummy type and emits a deprecation warning.
-See :ref:`instance-collector-deprecation` for details.
diff --git a/changelog/9308.breaking.rst b/changelog/9308.breaking.rst
deleted file mode 100644
index b03a854aa2f..00000000000
--- a/changelog/9308.breaking.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-**PytestRemovedIn7Warning deprecation warnings are now errors by default.**
-
-Following our plan to remove deprecated features with as little disruption as
-possible, all warnings of type ``PytestRemovedIn7Warning`` now generate errors
-instead of warning messages by default.
-
-**The affected features will be effectively removed in pytest 7.1**, so please consult the
-:ref:`deprecations` section in the docs for directions on how to update existing code.
-
-In the pytest ``7.0.X`` series, it is possible to change the errors back into warnings as a
-stopgap measure by adding this to your ``pytest.ini`` file:
-
-.. code-block:: ini
-
-    [pytest]
-    filterwarnings =
-        ignore::pytest.PytestRemovedIn7Warning
-
-But this will stop working when pytest ``7.1`` is released.
-
-**If you have concerns** about the removal of a specific feature, please add a
-comment to :issue:`9308`.
diff --git a/changelog/9341.doc.rst b/changelog/9341.doc.rst
deleted file mode 100644
index 1b97a581247..00000000000
--- a/changelog/9341.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Various methods commonly used for :ref:`non-python tests` are now correctly documented in the reference docs. They were undocumented previously.
diff --git a/changelog/9351.trivial.rst b/changelog/9351.trivial.rst
deleted file mode 100644
index 4fd8ac82704..00000000000
--- a/changelog/9351.trivial.rst
+++ /dev/null
@@ -1 +0,0 @@
-Correct minor typos in doc/en/example/special.rst.
diff --git a/changelog/README.rst b/changelog/README.rst
index 6d026f57ef3..fdaa573d427 100644
--- a/changelog/README.rst
+++ b/changelog/README.rst
@@ -14,16 +14,28 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
 ``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:
 
 * ``feature``: new user facing features, like new command-line options and new behavior.
-* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junitxml``, improved colors in terminal, etc).
+* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junit-xml``, improved colors in terminal, etc).
 * ``bugfix``: fixes a bug.
 * ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
 * ``deprecation``: feature deprecation.
 * ``breaking``: a change which may break existing suites, such as feature removal or behavior change.
 * ``vendor``: changes in packages vendored in pytest.
-* ``trivial``: fixing a small typo or internal change that might be noteworthy.
+* ``packaging``: notes for downstreams about unobvious side effects
+  and tooling. changes in the test invocation considerations and
+  runtime assumptions.
+* ``contrib``: stuff that affects the contributor experience. e.g.
+  Running tests, building the docs, setting up the development
+  environment.
+* ``misc``: changes that are hard to assign to any of the above
+  categories.
 
 So for example: ``123.feature.rst``, ``456.bugfix.rst``.
 
+.. tip::
+
+   See :file:`pyproject.toml` for all available categories
+   (``tool.towncrier.type``).
+
 If your PR fixes an issue, use that number here. If there is no issue,
 then after you submit the PR and get the PR number you can add a
 changelog using that instead.
diff --git a/codecov.yml b/codecov.yml
index f1cc8697338..c37e5ec4a09 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,6 +1,13 @@
 # reference: https://docs.codecov.io/docs/codecovyml-reference
+---
+
+codecov:
+  token: 1eca3b1f-31a2-4fb8-a8c3-138b441b50a7 #repo token
+
 coverage:
   status:
-    patch: true
+    patch:
+      default:
+        target: 100%  # require patches to be 100%
     project: false
 comment: false
diff --git a/doc/en/_static/pytest-custom.css b/doc/en/_static/pytest-custom.css
new file mode 100644
index 00000000000..bc9eef457f1
--- /dev/null
+++ b/doc/en/_static/pytest-custom.css
@@ -0,0 +1,21 @@
+/* Tweak how the sidebar logo is presented */
+.sidebar-logo {
+  width: 70%;
+}
+.sidebar-brand {
+  padding: 0;
+}
+
+/* The landing pages' sidebar-in-content highlights */
+#features ul {
+  padding-left: 1rem;
+  list-style: none;
+}
+#features ul li {
+  margin-bottom: 0;
+}
+@media (min-width: 46em) {
+  #features {
+    width: 50%;
+  }
+}
diff --git a/doc/en/img/pytest1.png b/doc/en/_static/pytest1.png
similarity index 100%
rename from doc/en/img/pytest1.png
rename to doc/en/_static/pytest1.png
diff --git a/doc/en/_templates/globaltoc.html b/doc/en/_templates/globaltoc.html
deleted file mode 100644
index 7c595e7ebf2..00000000000
--- a/doc/en/_templates/globaltoc.html
+++ /dev/null
@@ -1,34 +0,0 @@
-<h3>Contents</h3>
-
-<ul>
-  <li><a href="{{ pathto('index') }}">Home</a></li>
-
-  <li><a href="{{ pathto('getting-started') }}">Get started</a></li>
-  <li><a href="{{ pathto('how-to/index') }}">How-to guides</a></li>
-  <li><a href="{{ pathto('reference/index') }}">Reference guides</a></li>
-  <li><a href="{{ pathto('explanation/index') }}">Explanation</a></li>
-  <li><a href="{{ pathto('contents') }}">Complete table of contents</a></li>
-  <li><a href="{{ pathto('example/index') }}">Library of examples</a></li>
-</ul>
-
-<h3>About the project</h3>
-
-<ul>
-  <li><a href="{{ pathto('changelog') }}">Changelog</a></li>
-  <li><a href="{{ pathto('contributing') }}">Contributing</a></li>
-  <li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li>
-  <li><a href="{{ pathto('py27-py34-deprecation') }}">Python 2.7 and 3.4 Support</a></li>
-  <li><a href="{{ pathto('sponsor') }}">Sponsor</a></li>
-  <li><a href="{{ pathto('tidelift') }}">pytest for Enterprise</a></li>
-  <li><a href="{{ pathto('license') }}">License</a></li>
-  <li><a href="{{ pathto('contact') }}">Contact Channels</a></li>
-</ul>
-
-{%- if display_toc %}
-  <hr>
-  {{ toc }}
-{%- endif %}
-
-<hr>
-<a href="{{ pathto('genindex') }}">Index</a>
-<hr>
diff --git a/doc/en/_templates/layout.html b/doc/en/_templates/layout.html
deleted file mode 100644
index f7096eaaa5e..00000000000
--- a/doc/en/_templates/layout.html
+++ /dev/null
@@ -1,52 +0,0 @@
-{#
-
-    Copied from:
-
-     https://raw.githubusercontent.com/pallets/pallets-sphinx-themes/b0c6c41849b4e15cbf62cc1d95c05ef2b3e155c8/src/pallets_sphinx_themes/themes/pocoo/layout.html
-
-    And removed the warning version (see #7331).
-
-#}
-
-{% extends "basic/layout.html" %}
-
-{% set metatags %}
-  {{- metatags }}
-  <meta name="viewport" content="width=device-width, initial-scale=1">
-{%- endset %}
-
-{% block extrahead %}
-  {%- if page_canonical_url %}
-    <link rel="canonical" href="{{ page_canonical_url }}">
-  {%- endif %}
-  <script>DOCUMENTATION_OPTIONS.URL_ROOT = '{{ url_root }}';</script>
-  {{ super() }}
-{%- endblock %}
-
-{% block sidebarlogo %}
-  {% if pagename != "index" or theme_index_sidebar_logo %}
-    {{ super() }}
-  {% endif %}
-{% endblock %}
-
-{% block relbar2 %}{% endblock %}
-
-{% block sidebar2 %}
-  <span id="sidebar-top"></span>
-  {{- super() }}
-{%- endblock %}
-
-{% block footer %}
-  {{ super() }}
-  {%- if READTHEDOCS and not readthedocs_docsearch %}
-    <script>
-      if (typeof READTHEDOCS_DATA !== 'undefined') {
-        if (!READTHEDOCS_DATA.features) {
-          READTHEDOCS_DATA.features = {};
-        }
-        READTHEDOCS_DATA.features.docsearch_disabled = true;
-      }
-    </script>
-  {%- endif %}
-  {{ js_tag("_static/version_warning_offset.js") }}
-{% endblock %}
diff --git a/doc/en/_templates/relations.html b/doc/en/_templates/relations.html
deleted file mode 100644
index 3bbcde85bb4..00000000000
--- a/doc/en/_templates/relations.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<h3>Related Topics</h3>
-<ul>
-  <li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
-  {%- for parent in parents %}
-  <li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
-  {%- endfor %}
-    {%- if prev %}
-      <li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
-        }}">{{ prev.title }}</a></li>
-    {%- endif %}
-    {%- if next %}
-      <li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
-        }}">{{ next.title }}</a></li>
-    {%- endif %}
-  {%- for parent in parents %}
-  </ul></li>
-  {%- endfor %}
-  </ul></li>
-</ul>
diff --git a/doc/en/_templates/sidebarintro.html b/doc/en/_templates/sidebarintro.html
deleted file mode 100644
index ae860c172f0..00000000000
--- a/doc/en/_templates/sidebarintro.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<h3>About pytest</h3>
-<p>
-  pytest is a mature full-featured Python testing tool that helps
-  you write better programs.
-</p>
diff --git a/doc/en/_templates/slim_searchbox.html b/doc/en/_templates/slim_searchbox.html
deleted file mode 100644
index e98ad4ed905..00000000000
--- a/doc/en/_templates/slim_searchbox.html
+++ /dev/null
@@ -1,15 +0,0 @@
-{#
-    basic/searchbox.html with heading removed.
-#}
-{%- if pagename != "search" and builder != "singlehtml" %}
-<div id="searchbox" style="display: none" role="search">
-  <div class="searchformwrapper">
-    <form class="search" action="{{ pathto('search') }}" method="get">
-      <input type="text" name="q" aria-labelledby="searchlabel"
-        placeholder="Search"/>
-      <input type="submit" value="{{ _('Go') }}" />
-    </form>
-  </div>
-</div>
-<script type="text/javascript">$('#searchbox').show(0);</script>
-{%- endif %}
diff --git a/doc/en/adopt.rst b/doc/en/adopt.rst
index 13d82bf0116..b95a117debb 100644
--- a/doc/en/adopt.rst
+++ b/doc/en/adopt.rst
@@ -44,7 +44,7 @@ Partner projects, sign up here! (by 22 March)
 What does it mean to "adopt pytest"?
 -----------------------------------------
 
-There can be many different definitions of "success". Pytest can run many nose_ and unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right?
+There can be many different definitions of "success". Pytest can run many unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right?
 
 Progressive success might look like:
 
@@ -62,7 +62,6 @@ Progressive success might look like:
 
 It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies.
 
-.. _nose: nose.html
 .. _unittest: unittest.html
 .. _assert: assert.html
 .. _pycmd: https://bitbucket.org/hpk42/pycmd/overview
diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst
index f0c84cb4c47..51edc964a0c 100644
--- a/doc/en/announce/index.rst
+++ b/doc/en/announce/index.rst
@@ -6,6 +6,41 @@ Release announcements
    :maxdepth: 2
 
 
+   release-8.3.5
+   release-8.3.4
+   release-8.3.3
+   release-8.3.2
+   release-8.3.1
+   release-8.3.0
+   release-8.2.2
+   release-8.2.1
+   release-8.2.0
+   release-8.1.2
+   release-8.1.1
+   release-8.1.0
+   release-8.0.2
+   release-8.0.1
+   release-8.0.0
+   release-8.0.0rc2
+   release-8.0.0rc1
+   release-7.4.4
+   release-7.4.3
+   release-7.4.2
+   release-7.4.1
+   release-7.4.0
+   release-7.3.2
+   release-7.3.1
+   release-7.3.0
+   release-7.2.2
+   release-7.2.1
+   release-7.2.0
+   release-7.1.3
+   release-7.1.2
+   release-7.1.1
+   release-7.1.0
+   release-7.0.1
+   release-7.0.0
+   release-7.0.0rc1
    release-6.2.5
    release-6.2.4
    release-6.2.3
diff --git a/doc/en/announce/release-2.0.0.rst b/doc/en/announce/release-2.0.0.rst
index ecb1a1db988..c2a9f6da4d5 100644
--- a/doc/en/announce/release-2.0.0.rst
+++ b/doc/en/announce/release-2.0.0.rst
@@ -62,7 +62,7 @@ New Features
 - new "-q" option which decreases verbosity and prints a more
   nose/unittest-style "dot" output.
 
-- many many more detailed improvements details
+- many, many, more detailed improvements details
 
 Fixes
 -----------------------
@@ -109,7 +109,7 @@ Important Notes
     in conftest.py files.  They will cause nothing special.
   - removed support for calling the pre-1.0 collection API of "run()" and "join"
   - removed reading option values from conftest.py files or env variables.
-    This can now be done much much better and easier through the ini-file
+    This can now be done much, much, better and easier through the ini-file
     mechanism and the "addopts" entry in particular.
   - removed the "disabled" attribute in test classes.  Use the skipping
     and pytestmark mechanism to skip or xfail a test class.
diff --git a/doc/en/announce/release-2.2.2.rst b/doc/en/announce/release-2.2.2.rst
index 22ef0bc7a16..510b35ee1d0 100644
--- a/doc/en/announce/release-2.2.2.rst
+++ b/doc/en/announce/release-2.2.2.rst
@@ -4,7 +4,7 @@ pytest-2.2.2: bug fixes
 pytest-2.2.2 (updated to 2.2.3 to fix packaging issues) is a minor
 backward-compatible release of the versatile py.test testing tool.   It
 contains bug fixes and a few refinements particularly to reporting with
-"--collectonly", see below for betails.
+"--collectonly", see below for details.
 
 For general information see here:
 
diff --git a/doc/en/announce/release-2.3.0.rst b/doc/en/announce/release-2.3.0.rst
index 6905b77b923..c405073ef40 100644
--- a/doc/en/announce/release-2.3.0.rst
+++ b/doc/en/announce/release-2.3.0.rst
@@ -6,10 +6,10 @@ and parametrized testing in Python.  It is now easier, more efficient and
 more predictable to re-run the same tests with different fixture
 instances.  Also, you can directly declare the caching "scope" of
 fixtures so that dependent tests throughout your whole test suite can
-re-use database or other expensive fixture objects with ease.  Lastly,
+reuse database or other expensive fixture objects with ease.  Lastly,
 it's possible for fixture functions (formerly known as funcarg
 factories) to use other fixtures, allowing for a completely modular and
-re-usable fixture design.
+reusable fixture design.
 
 For detailed info and tutorial-style examples, see:
 
diff --git a/doc/en/announce/release-2.4.0.rst b/doc/en/announce/release-2.4.0.rst
index 138cc89576c..9b864329674 100644
--- a/doc/en/announce/release-2.4.0.rst
+++ b/doc/en/announce/release-2.4.0.rst
@@ -181,7 +181,7 @@ Bug fixes:
   partially failed (finalizers would not always be called before)
 
 - fix issue320 - fix class scope for fixtures when mixed with
-  module-level functions.  Thanks Anatloy Bubenkoff.
+  module-level functions.  Thanks Anatoly Bubenkoff.
 
 - you can specify "-q" or "-qq" to get different levels of "quieter"
   reporting (thanks Katarzyna Jachim)
diff --git a/doc/en/announce/release-2.5.0.rst b/doc/en/announce/release-2.5.0.rst
index bc83fdc122c..fe64f1b8668 100644
--- a/doc/en/announce/release-2.5.0.rst
+++ b/doc/en/announce/release-2.5.0.rst
@@ -11,7 +11,7 @@ clear information about the circumstances and a simple example which
 reproduces the problem.
 
 The issue tracker is of course not empty now.  We have many remaining
-"enhacement" issues which we'll hopefully can tackle in 2014 with your
+"enhancement" issues which we'll hopefully can tackle in 2014 with your
 help.
 
 For those who use older Python versions, please note that pytest is not
@@ -83,7 +83,7 @@ holger krekel
   Thanks Ralph Schmitt for the precise failure example.
 
 - fix issue244 by implementing special index for parameters to only use
-  indices for paramentrized test ids
+  indices for parametrized test ids
 
 - fix issue287 by running all finalizers but saving the exception
   from the first failing finalizer and re-raising it so teardown will
diff --git a/doc/en/announce/release-2.6.0.rst b/doc/en/announce/release-2.6.0.rst
index 56fbd6cc1e4..c00df585738 100644
--- a/doc/en/announce/release-2.6.0.rst
+++ b/doc/en/announce/release-2.6.0.rst
@@ -73,7 +73,7 @@ holger krekel
 - cleanup setup.py a bit and specify supported versions. Thanks Jurko
   Gospodnetic for the PR.
 
-- change XPASS colour to yellow rather then red when tests are run
+- change XPASS colour to yellow rather than red when tests are run
   with -v.
 
 - fix issue473: work around mock putting an unbound method into a class
diff --git a/doc/en/announce/release-2.7.0.rst b/doc/en/announce/release-2.7.0.rst
index 2840178a07f..83cddb34157 100644
--- a/doc/en/announce/release-2.7.0.rst
+++ b/doc/en/announce/release-2.7.0.rst
@@ -55,7 +55,7 @@ holger krekel
   github.  See https://pytest.org/en/stable/contributing.html .
   Thanks to Anatoly for pushing and initial work on this.
 
-- fix issue650: new option ``--docttest-ignore-import-errors`` which
+- fix issue650: new option ``--doctest-ignore-import-errors`` which
   will turn import errors in doctests into skips.  Thanks Charles Cloud
   for the complete PR.
 
diff --git a/doc/en/announce/release-2.9.0.rst b/doc/en/announce/release-2.9.0.rst
index 3aea08cb225..753bb7bf6f0 100644
--- a/doc/en/announce/release-2.9.0.rst
+++ b/doc/en/announce/release-2.9.0.rst
@@ -45,7 +45,7 @@ The py.test Development Team
 **New Features**
 
 * New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
-  Thanks :user:`MichaelAquilina` for the complete PR (:pull:`1040`).
+  Thanks :user:`MichaelAquilina` for the complete PR (:pr:`1040`).
 
 * ``--doctest-glob`` may now be passed multiple times in the command-line.
   Thanks :user:`jab` and :user:`nicoddemus` for the PR.
diff --git a/doc/en/announce/release-2.9.1.rst b/doc/en/announce/release-2.9.1.rst
index 6a627ad3cd6..7a46d2ae690 100644
--- a/doc/en/announce/release-2.9.1.rst
+++ b/doc/en/announce/release-2.9.1.rst
@@ -44,7 +44,7 @@ The py.test Development Team
   Thanks :user:`nicoddemus` for the PR.
 
 * Fix (:issue:`469`): junit parses report.nodeid incorrectly, when params IDs
-  contain ``::``. Thanks :user:`tomviner` for the PR (:pull:`1431`).
+  contain ``::``. Thanks :user:`tomviner` for the PR (:pr:`1431`).
 
 * Fix (:issue:`578`): SyntaxErrors
   containing non-ascii lines at the point of failure generated an internal
diff --git a/doc/en/announce/release-2.9.2.rst b/doc/en/announce/release-2.9.2.rst
index 2dc82a1117b..3e75af7fe69 100644
--- a/doc/en/announce/release-2.9.2.rst
+++ b/doc/en/announce/release-2.9.2.rst
@@ -44,14 +44,14 @@ The py.test Development Team
 
 * Fix Xfail does not work with condition keyword argument.
   Thanks :user:`astraw38` for reporting the issue (:issue:`1496`) and :user:`tomviner`
-  for PR the (:pull:`1524`).
+  for PR the (:pr:`1524`).
 
 * Fix win32 path issue when putting custom config file with absolute path
   in ``pytest.main("-c your_absolute_path")``.
 
 * Fix maximum recursion depth detection when raised error class is not aware
   of unicode/encoded bytes.
-  Thanks :user:`prusse-martin` for the PR (:pull:`1506`).
+  Thanks :user:`prusse-martin` for the PR (:pr:`1506`).
 
 * Fix ``pytest.mark.skip`` mark when used in strict mode.
   Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for
diff --git a/doc/en/announce/release-7.0.0.rst b/doc/en/announce/release-7.0.0.rst
new file mode 100644
index 00000000000..3ce4335564f
--- /dev/null
+++ b/doc/en/announce/release-7.0.0.rst
@@ -0,0 +1,74 @@
+pytest-7.0.0
+=======================================
+
+The pytest team is proud to announce the 7.0.0 release!
+
+This release contains new features, improvements, bug fixes, and breaking changes, so users
+are encouraged to take a look at the CHANGELOG carefully:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Adam J. Stewart
+* Alexander King
+* Amin Alaee
+* Andrew Neitsch
+* Anthony Sottile
+* Ben Davies
+* Bernát Gábor
+* Brian Okken
+* Bruno Oliveira
+* Cristian Vera
+* Dan Alvizu
+* David Szotten
+* Eddie
+* Emmanuel Arias
+* Emmanuel Meric de Bellefon
+* Eric Liu
+* Florian Bruhin
+* GergelyKalmar
+* Graeme Smecher
+* Harshna
+* Hugo van Kemenade
+* Jakub Kulík
+* James Myatt
+* Jeff Rasley
+* Kale Kundert
+* Kian Meng, Ang
+* Miro Hrončok
+* Naveen-Pratap
+* Oleg Höfling
+* Olga Matoula
+* Ran Benita
+* Ronny Pfannschmidt
+* Simon K
+* Srip
+* Sören Wegener
+* Taneli Hukkinen
+* Terje Runde
+* Thomas Grainger
+* Thomas Hisch
+* William Jamir Silva
+* Yuval Shimon
+* Zac Hatfield-Dodds
+* andrewdotn
+* denivyruck
+* ericluoliu
+* oleg.hoefling
+* symonk
+* ziebam
+* Éloi Rivard
+* Éric
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.0.0rc1.rst b/doc/en/announce/release-7.0.0rc1.rst
new file mode 100644
index 00000000000..a5bf0ed3c44
--- /dev/null
+++ b/doc/en/announce/release-7.0.0rc1.rst
@@ -0,0 +1,74 @@
+pytest-7.0.0rc1
+=======================================
+
+The pytest team is proud to announce the 7.0.0rc1 prerelease!
+
+This is a prerelease, not intended for production use, but to test the upcoming features and improvements
+in order to catch any major problems before the final version is released to the major public.
+
+We appreciate your help testing this out before the final release, making sure to report any
+regressions to our issue tracker:
+
+https://github.com/pytest-dev/pytest/issues
+
+When doing so, please include the string ``[prerelease]`` in the title.
+
+You can upgrade from PyPI via:
+
+    pip install pytest==7.0.0rc1
+
+Users are encouraged to take a look at the CHANGELOG carefully:
+
+    https://docs.pytest.org/en/7.0.x/changelog.html
+
+Thanks to all the contributors to this release:
+
+* Adam J. Stewart
+* Alexander King
+* Amin Alaee
+* Andrew Neitsch
+* Anthony Sottile
+* Ben Davies
+* Bernát Gábor
+* Brian Okken
+* Bruno Oliveira
+* Cristian Vera
+* David Szotten
+* Eddie
+* Emmanuel Arias
+* Emmanuel Meric de Bellefon
+* Eric Liu
+* Florian Bruhin
+* GergelyKalmar
+* Graeme Smecher
+* Harshna
+* Hugo van Kemenade
+* Jakub Kulík
+* James Myatt
+* Jeff Rasley
+* Kale Kundert
+* Miro Hrončok
+* Naveen-Pratap
+* Oleg Höfling
+* Ran Benita
+* Ronny Pfannschmidt
+* Simon K
+* Srip
+* Sören Wegener
+* Taneli Hukkinen
+* Terje Runde
+* Thomas Grainger
+* Thomas Hisch
+* William Jamir Silva
+* Zac Hatfield-Dodds
+* andrewdotn
+* denivyruck
+* ericluoliu
+* oleg.hoefling
+* symonk
+* ziebam
+* Éloi Rivard
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.0.1.rst b/doc/en/announce/release-7.0.1.rst
new file mode 100644
index 00000000000..5accfbad0d4
--- /dev/null
+++ b/doc/en/announce/release-7.0.1.rst
@@ -0,0 +1,20 @@
+pytest-7.0.1
+=======================================
+
+pytest 7.0.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Anthony Sottile
+* Bruno Oliveira
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.1.0.rst b/doc/en/announce/release-7.1.0.rst
new file mode 100644
index 00000000000..3361e1c8a32
--- /dev/null
+++ b/doc/en/announce/release-7.1.0.rst
@@ -0,0 +1,48 @@
+pytest-7.1.0
+=======================================
+
+The pytest team is proud to announce the 7.1.0 release!
+
+This release contains new features, improvements, and bug fixes,
+the full list of changes is available in the changelog:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Akuli
+* Andrew Svetlov
+* Anthony Sottile
+* Brett Holman
+* Bruno Oliveira
+* Chris NeJame
+* Dan Alvizu
+* Elijah DeLee
+* Emmanuel Arias
+* Fabian Egli
+* Florian Bruhin
+* Gabor Szabo
+* Hasan Ramezani
+* Hugo van Kemenade
+* Kian Meng, Ang
+* Kojo Idrissa
+* Masaru Tsuchiyama
+* Olga Matoula
+* P. L. Lim
+* Ran Benita
+* Tobias Deiminger
+* Yuval Shimon
+* eduardo naufel schettino
+* Éric
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.1.1.rst b/doc/en/announce/release-7.1.1.rst
new file mode 100644
index 00000000000..d271c4557a2
--- /dev/null
+++ b/doc/en/announce/release-7.1.1.rst
@@ -0,0 +1,18 @@
+pytest-7.1.1
+=======================================
+
+pytest 7.1.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.1.2.rst b/doc/en/announce/release-7.1.2.rst
new file mode 100644
index 00000000000..ba33cdc694b
--- /dev/null
+++ b/doc/en/announce/release-7.1.2.rst
@@ -0,0 +1,23 @@
+pytest-7.1.2
+=======================================
+
+pytest 7.1.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Anthony Sottile
+* Bruno Oliveira
+* Hugo van Kemenade
+* Kian Eliasi
+* Ran Benita
+* Zac Hatfield-Dodds
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.1.3.rst b/doc/en/announce/release-7.1.3.rst
new file mode 100644
index 00000000000..4cb1b271704
--- /dev/null
+++ b/doc/en/announce/release-7.1.3.rst
@@ -0,0 +1,28 @@
+pytest-7.1.3
+=======================================
+
+pytest 7.1.3 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Anthony Sottile
+* Bruno Oliveira
+* Gergely Kalmár
+* Nipunn Koorapati
+* Pax
+* Sviatoslav Sydorenko
+* Tim Hoffmann
+* Tony Narlock
+* Wolfremium
+* Zach OBrien
+* aizpurua23a
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.2.0.rst b/doc/en/announce/release-7.2.0.rst
new file mode 100644
index 00000000000..eca84aeb669
--- /dev/null
+++ b/doc/en/announce/release-7.2.0.rst
@@ -0,0 +1,93 @@
+pytest-7.2.0
+=======================================
+
+The pytest team is proud to announce the 7.2.0 release!
+
+This release contains new features, improvements, and bug fixes,
+the full list of changes is available in the changelog:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Aaron Berdy
+* Adam Turner
+* Albert Villanova del Moral
+* Alice Purcell
+* Anthony Sottile
+* Anton Yakutovich
+* Babak Keyvani
+* Brandon Chinn
+* Bruno Oliveira
+* Chanvin Xiao
+* Cheuk Ting Ho
+* Chris Wheeler
+* EmptyRabbit
+* Ezio Melotti
+* Florian Best
+* Florian Bruhin
+* Fredrik Berndtsson
+* Gabriel Landau
+* Gergely Kalmár
+* Hugo van Kemenade
+* James Gerity
+* John Litborn
+* Jon Parise
+* Kevin C
+* Kian Eliasi
+* MatthewFlamm
+* Miro Hrončok
+* Nate Meyvis
+* Neil Girdhar
+* Nhieuvu1802
+* Nipunn Koorapati
+* Ofek Lev
+* Paul Müller
+* Paul Reece
+* Pax
+* Pete Baughman
+* Peyman Salehi
+* Philipp A
+* Ran Benita
+* Robert O'Shea
+* Ronny Pfannschmidt
+* Rowin
+* Ruth Comer
+* Samuel Colvin
+* Samuel Gaist
+* Sandro Tosi
+* Shantanu
+* Simon K
+* Stephen Rosen
+* Sviatoslav Sydorenko
+* Tatiana Ovary
+* Thierry Moisan
+* Thomas Grainger
+* Tim Hoffmann
+* Tobias Diez
+* Tony Narlock
+* Vivaan Verma
+* Wolfremium
+* Zac Hatfield-Dodds
+* Zach OBrien
+* aizpurua23a
+* gresm
+* holesch
+* itxasos23
+* johnkangw
+* skhomuti
+* sommersoft
+* wodny
+* zx.qiu
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.2.1.rst b/doc/en/announce/release-7.2.1.rst
new file mode 100644
index 00000000000..80ac7aff07f
--- /dev/null
+++ b/doc/en/announce/release-7.2.1.rst
@@ -0,0 +1,25 @@
+pytest-7.2.1
+=======================================
+
+pytest 7.2.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Anthony Sottile
+* Bruno Oliveira
+* Daniel Valenzuela
+* Kadino
+* Prerak Patel
+* Ronny Pfannschmidt
+* Santiago Castro
+* s-padmanaban
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.2.2.rst b/doc/en/announce/release-7.2.2.rst
new file mode 100644
index 00000000000..b34a6ff5c1e
--- /dev/null
+++ b/doc/en/announce/release-7.2.2.rst
@@ -0,0 +1,25 @@
+pytest-7.2.2
+=======================================
+
+pytest 7.2.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Garvit Shubham
+* Mahesh Vashishtha
+* Ramsey
+* Ronny Pfannschmidt
+* Teejay
+* q0w
+* vin01
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.3.0.rst b/doc/en/announce/release-7.3.0.rst
new file mode 100644
index 00000000000..33258dabade
--- /dev/null
+++ b/doc/en/announce/release-7.3.0.rst
@@ -0,0 +1,130 @@
+pytest-7.3.0
+=======================================
+
+The pytest team is proud to announce the 7.3.0 release!
+
+This release contains new features, improvements, and bug fixes,
+the full list of changes is available in the changelog:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Aaron Berdy
+* Adam Turner
+* Albert Villanova del Moral
+* Alessio Izzo
+* Alex Hadley
+* Alice Purcell
+* Anthony Sottile
+* Anton Yakutovich
+* Ashish Kurmi
+* Babak Keyvani
+* Billy
+* Brandon Chinn
+* Bruno Oliveira
+* Cal Jacobson
+* Chanvin Xiao
+* Cheuk Ting Ho
+* Chris Wheeler
+* Daniel Garcia Moreno
+* Daniel Scheffler
+* Daniel Valenzuela
+* EmptyRabbit
+* Ezio Melotti
+* Felix Hofstätter
+* Florian Best
+* Florian Bruhin
+* Fredrik Berndtsson
+* Gabriel Landau
+* Garvit Shubham
+* Gergely Kalmár
+* HTRafal
+* Hugo van Kemenade
+* Ilya Konstantinov
+* Itxaso Aizpurua
+* James Gerity
+* Jay
+* John Litborn
+* Jon Parise
+* Jouke Witteveen
+* Kadino
+* Kevin C
+* Kian Eliasi
+* Klaus Rettinghaus
+* Kodi Arfer
+* Mahesh Vashishtha
+* Manuel Jacob
+* Marko Pacak
+* MatthewFlamm
+* Miro Hrončok
+* Nate Meyvis
+* Neil Girdhar
+* Nhieuvu1802
+* Nipunn Koorapati
+* Ofek Lev
+* Paul Kehrer
+* Paul Müller
+* Paul Reece
+* Pax
+* Pete Baughman
+* Peyman Salehi
+* Philipp A
+* Pierre Sassoulas
+* Prerak Patel
+* Ramsey
+* Ran Benita
+* Robert O'Shea
+* Ronny Pfannschmidt
+* Rowin
+* Ruth Comer
+* Samuel Colvin
+* Samuel Gaist
+* Sandro Tosi
+* Santiago Castro
+* Shantanu
+* Simon K
+* Stefanie Molin
+* Stephen Rosen
+* Sviatoslav Sydorenko
+* Tatiana Ovary
+* Teejay
+* Thierry Moisan
+* Thomas Grainger
+* Tim Hoffmann
+* Tobias Diez
+* Tony Narlock
+* Vivaan Verma
+* Wolfremium
+* Yannick PÉROUX
+* Yusuke Kadowaki
+* Zac Hatfield-Dodds
+* Zach OBrien
+* aizpurua23a
+* bitzge
+* bluthej
+* gresm
+* holesch
+* itxasos23
+* johnkangw
+* q0w
+* rdb
+* s-padmanaban
+* skhomuti
+* sommersoft
+* vin01
+* wim glenn
+* wodny
+* zx.qiu
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.3.1.rst b/doc/en/announce/release-7.3.1.rst
new file mode 100644
index 00000000000..e920fa8af53
--- /dev/null
+++ b/doc/en/announce/release-7.3.1.rst
@@ -0,0 +1,18 @@
+pytest-7.3.1
+=======================================
+
+pytest 7.3.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.3.2.rst b/doc/en/announce/release-7.3.2.rst
new file mode 100644
index 00000000000..b3b112f0d8e
--- /dev/null
+++ b/doc/en/announce/release-7.3.2.rst
@@ -0,0 +1,21 @@
+pytest-7.3.2
+=======================================
+
+pytest 7.3.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Adam J. Stewart
+* Alessio Izzo
+* Bruno Oliveira
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.4.0.rst b/doc/en/announce/release-7.4.0.rst
new file mode 100644
index 00000000000..5a0d18267d3
--- /dev/null
+++ b/doc/en/announce/release-7.4.0.rst
@@ -0,0 +1,49 @@
+pytest-7.4.0
+=======================================
+
+The pytest team is proud to announce the 7.4.0 release!
+
+This release contains new features, improvements, and bug fixes,
+the full list of changes is available in the changelog:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Adam J. Stewart
+* Alessio Izzo
+* Alex
+* Alex Lambson
+* Brian Larsen
+* Bruno Oliveira
+* Bryan Ricker
+* Chris Mahoney
+* Facundo Batista
+* Florian Bruhin
+* Jarrett Keifer
+* Kenny Y
+* Miro Hrončok
+* Ran Benita
+* Roberto Aldera
+* Ronny Pfannschmidt
+* Sergey Kim
+* Stefanie Molin
+* Vijay Arora
+* Ville Skyttä
+* Zac Hatfield-Dodds
+* bzoracler
+* leeyueh
+* nondescryptid
+* theirix
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.4.1.rst b/doc/en/announce/release-7.4.1.rst
new file mode 100644
index 00000000000..efadcf919e8
--- /dev/null
+++ b/doc/en/announce/release-7.4.1.rst
@@ -0,0 +1,20 @@
+pytest-7.4.1
+=======================================
+
+pytest 7.4.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Florian Bruhin
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.4.2.rst b/doc/en/announce/release-7.4.2.rst
new file mode 100644
index 00000000000..22191e7b4f9
--- /dev/null
+++ b/doc/en/announce/release-7.4.2.rst
@@ -0,0 +1,18 @@
+pytest-7.4.2
+=======================================
+
+pytest 7.4.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.4.3.rst b/doc/en/announce/release-7.4.3.rst
new file mode 100644
index 00000000000..0f319c1e7f0
--- /dev/null
+++ b/doc/en/announce/release-7.4.3.rst
@@ -0,0 +1,19 @@
+pytest-7.4.3
+=======================================
+
+pytest 7.4.3 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Marc Mueller
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-7.4.4.rst b/doc/en/announce/release-7.4.4.rst
new file mode 100644
index 00000000000..c9633678d2e
--- /dev/null
+++ b/doc/en/announce/release-7.4.4.rst
@@ -0,0 +1,20 @@
+pytest-7.4.4
+=======================================
+
+pytest 7.4.4 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Ran Benita
+* Zac Hatfield-Dodds
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.0.0.rst b/doc/en/announce/release-8.0.0.rst
new file mode 100644
index 00000000000..00f54fd8225
--- /dev/null
+++ b/doc/en/announce/release-8.0.0.rst
@@ -0,0 +1,26 @@
+pytest-8.0.0
+=======================================
+
+The pytest team is proud to announce the 8.0.0 release!
+
+This release contains new features, improvements, bug fixes, and breaking changes, so users
+are encouraged to take a look at the CHANGELOG carefully:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.0.0rc1.rst b/doc/en/announce/release-8.0.0rc1.rst
new file mode 100644
index 00000000000..547c8cbc53b
--- /dev/null
+++ b/doc/en/announce/release-8.0.0rc1.rst
@@ -0,0 +1,82 @@
+pytest-8.0.0rc1
+=======================================
+
+The pytest team is proud to announce the 8.0.0rc1 release!
+
+This release contains new features, improvements, bug fixes, and breaking changes, so users
+are encouraged to take a look at the CHANGELOG carefully:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Akhilesh Ramakrishnan
+* Aleksandr Brodin
+* Anthony Sottile
+* Arthur Richard
+* Avasam
+* Benjamin Schubert
+* Bruno Oliveira
+* Carsten Grohmann
+* Cheukting
+* Chris Mahoney
+* Christoph Anton Mitterer
+* DetachHead
+* Erik Hasse
+* Florian Bruhin
+* Fraser Stark
+* Ha Pam
+* Hugo van Kemenade
+* Isaac Virshup
+* Israel Fruchter
+* Jens Tröger
+* Jon Parise
+* Kenny Y
+* Lesnek
+* Marc Mueller
+* Michał Górny
+* Mihail Milushev
+* Milan Lesnek
+* Miro Hrončok
+* Patrick Lannigan
+* Ran Benita
+* Reagan Lee
+* Ronny Pfannschmidt
+* Sadra Barikbin
+* Sean Malloy
+* Sean Patrick Malloy
+* Sharad Nair
+* Simon Blanchard
+* Sourabh Beniwal
+* Stefaan Lippens
+* Tanya Agarwal
+* Thomas Grainger
+* Tom Mortimer-Jones
+* Tushar Sadhwani
+* Tyler Smart
+* Uday Kumar
+* Warren Markham
+* WarrenTheRabbit
+* Zac Hatfield-Dodds
+* Ziad Kermadi
+* akhilramkee
+* antosikv
+* bowugit
+* mickeypash
+* neilmartin2000
+* pomponchik
+* ryanpudd
+* touilleWoman
+* ubaumann
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.0.0rc2.rst b/doc/en/announce/release-8.0.0rc2.rst
new file mode 100644
index 00000000000..1a6444c5214
--- /dev/null
+++ b/doc/en/announce/release-8.0.0rc2.rst
@@ -0,0 +1,32 @@
+pytest-8.0.0rc2
+=======================================
+
+The pytest team is proud to announce the 8.0.0rc2 prerelease!
+
+This is a prerelease, not intended for production use, but to test the upcoming features and improvements
+in order to catch any major problems before the final version is released to the major public.
+
+We appreciate your help testing this out before the final release, making sure to report any
+regressions to our issue tracker:
+
+https://github.com/pytest-dev/pytest/issues
+
+When doing so, please include the string ``[prerelease]`` in the title.
+
+You can upgrade from PyPI via:
+
+    pip install pytest==8.0.0rc2
+
+Users are encouraged to take a look at the CHANGELOG carefully:
+
+    https://docs.pytest.org/en/release-8.0.0rc2/changelog.html
+
+Thanks to all the contributors to this release:
+
+* Ben Brown
+* Bruno Oliveira
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.0.1.rst b/doc/en/announce/release-8.0.1.rst
new file mode 100644
index 00000000000..7d828e55bd9
--- /dev/null
+++ b/doc/en/announce/release-8.0.1.rst
@@ -0,0 +1,21 @@
+pytest-8.0.1
+=======================================
+
+pytest 8.0.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Clément Robert
+* Pierre Sassoulas
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.0.2.rst b/doc/en/announce/release-8.0.2.rst
new file mode 100644
index 00000000000..c42159c57cf
--- /dev/null
+++ b/doc/en/announce/release-8.0.2.rst
@@ -0,0 +1,18 @@
+pytest-8.0.2
+=======================================
+
+pytest 8.0.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.1.0.rst b/doc/en/announce/release-8.1.0.rst
new file mode 100644
index 00000000000..62cafdd78bb
--- /dev/null
+++ b/doc/en/announce/release-8.1.0.rst
@@ -0,0 +1,54 @@
+pytest-8.1.0
+=======================================
+
+The pytest team is proud to announce the 8.1.0 release!
+
+This release contains new features, improvements, and bug fixes,
+the full list of changes is available in the changelog:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Ben Brown
+* Ben Leith
+* Bruno Oliveira
+* Clément Robert
+* Dave Hall
+* Dương Quốc Khánh
+* Eero Vaher
+* Eric Larson
+* Fabian Sturm
+* Faisal Fawad
+* Florian Bruhin
+* Franck Charras
+* Joachim B Haga
+* John Litborn
+* Loïc Estève
+* Marc Bresson
+* Patrick Lannigan
+* Pierre Sassoulas
+* Ran Benita
+* Reagan Lee
+* Ronny Pfannschmidt
+* Russell Martin
+* clee2000
+* donghui
+* faph
+* jakkdl
+* mrbean-bremen
+* robotherapist
+* whysage
+* woutdenolf
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.1.1.rst b/doc/en/announce/release-8.1.1.rst
new file mode 100644
index 00000000000..89b617b487d
--- /dev/null
+++ b/doc/en/announce/release-8.1.1.rst
@@ -0,0 +1,18 @@
+pytest-8.1.1
+=======================================
+
+pytest 8.1.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.1.2.rst b/doc/en/announce/release-8.1.2.rst
new file mode 100644
index 00000000000..19e41e0f7c5
--- /dev/null
+++ b/doc/en/announce/release-8.1.2.rst
@@ -0,0 +1,18 @@
+pytest-8.1.2
+=======================================
+
+pytest 8.1.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.2.0.rst b/doc/en/announce/release-8.2.0.rst
new file mode 100644
index 00000000000..2a63c8d8722
--- /dev/null
+++ b/doc/en/announce/release-8.2.0.rst
@@ -0,0 +1,43 @@
+pytest-8.2.0
+=======================================
+
+The pytest team is proud to announce the 8.2.0 release!
+
+This release contains new features, improvements, and bug fixes,
+the full list of changes is available in the changelog:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Daniel Miller
+* Florian Bruhin
+* HolyMagician03-UMich
+* John Litborn
+* Levon Saldamli
+* Linghao Zhang
+* Manuel López-Ibáñez
+* Pierre Sassoulas
+* Ran Benita
+* Ronny Pfannschmidt
+* Sebastian Meyer
+* Shekhar verma
+* Tamir Duberstein
+* Tobias Stoeckmann
+* dj
+* jakkdl
+* poulami-sau
+* tserg
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.2.1.rst b/doc/en/announce/release-8.2.1.rst
new file mode 100644
index 00000000000..4452edec110
--- /dev/null
+++ b/doc/en/announce/release-8.2.1.rst
@@ -0,0 +1,19 @@
+pytest-8.2.1
+=======================================
+
+pytest 8.2.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.2.2.rst b/doc/en/announce/release-8.2.2.rst
new file mode 100644
index 00000000000..3b1d93bd08b
--- /dev/null
+++ b/doc/en/announce/release-8.2.2.rst
@@ -0,0 +1,19 @@
+pytest-8.2.2
+=======================================
+
+pytest 8.2.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.3.0.rst b/doc/en/announce/release-8.3.0.rst
new file mode 100644
index 00000000000..ec5cd3d0db9
--- /dev/null
+++ b/doc/en/announce/release-8.3.0.rst
@@ -0,0 +1,60 @@
+pytest-8.3.0
+=======================================
+
+The pytest team is proud to announce the 8.3.0 release!
+
+This release contains new features, improvements, and bug fixes,
+the full list of changes is available in the changelog:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Anita Hammer
+* Ben Brown
+* Brian Okken
+* Bruno Oliveira
+* Cornelius Riemenschneider
+* Farbod Ahmadian
+* Florian Bruhin
+* Hynek Schlawack
+* James Frost
+* Jason R. Coombs
+* Jelle Zijlstra
+* Josh Soref
+* Marc Bresson
+* Michael Vogt
+* Nathan Goldbaum
+* Nicolas Simonds
+* Oliver Bestwalter
+* Pavel Březina
+* Pierre Sassoulas
+* Pradyun Gedam
+* Ran Benita
+* Ronny Pfannschmidt
+* SOUBHIK KUMAR MITRA
+* Sam Jirovec
+* Stavros Ntentos
+* Sviatoslav Sydorenko
+* Sviatoslav Sydorenko (Святослав Сидоренко)
+* Tomasz Kłoczko
+* Virendra Patil
+* Yutian Li
+* Zach Snicker
+* dj
+* holger krekel
+* joseph-sentry
+* lovetheguitar
+* neutraljump
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.3.1.rst b/doc/en/announce/release-8.3.1.rst
new file mode 100644
index 00000000000..0fb9b40d9c7
--- /dev/null
+++ b/doc/en/announce/release-8.3.1.rst
@@ -0,0 +1,19 @@
+pytest-8.3.1
+=======================================
+
+pytest 8.3.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.3.2.rst b/doc/en/announce/release-8.3.2.rst
new file mode 100644
index 00000000000..1e4a071692c
--- /dev/null
+++ b/doc/en/announce/release-8.3.2.rst
@@ -0,0 +1,19 @@
+pytest-8.3.2
+=======================================
+
+pytest 8.3.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Ran Benita
+* Ronny Pfannschmidt
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.3.3.rst b/doc/en/announce/release-8.3.3.rst
new file mode 100644
index 00000000000..5e3eb36b921
--- /dev/null
+++ b/doc/en/announce/release-8.3.3.rst
@@ -0,0 +1,31 @@
+pytest-8.3.3
+=======================================
+
+pytest 8.3.3 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Anthony Sottile
+* Avasam
+* Bruno Oliveira
+* Christian Clauss
+* Eugene Mwangi
+* Florian Bruhin
+* GTowers1
+* Nauman Ahmed
+* Pierre Sassoulas
+* Reagan Lee
+* Ronny Pfannschmidt
+* Stefaan Lippens
+* Sviatoslav Sydorenko (Святослав Сидоренко)
+* dongfangtianyu
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.3.4.rst b/doc/en/announce/release-8.3.4.rst
new file mode 100644
index 00000000000..f76d60396dc
--- /dev/null
+++ b/doc/en/announce/release-8.3.4.rst
@@ -0,0 +1,30 @@
+pytest-8.3.4
+=======================================
+
+pytest 8.3.4 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Florian Bruhin
+* Frank Hoffmann
+* Jakob van Santen
+* Leonardus Chen
+* Pierre Sassoulas
+* Pradeep Kumar
+* Ran Benita
+* Serge Smertin
+* Stefaan Lippens
+* Sviatoslav Sydorenko (Святослав Сидоренко)
+* dongfangtianyu
+* suspe
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/release-8.3.5.rst b/doc/en/announce/release-8.3.5.rst
new file mode 100644
index 00000000000..3de02c1d7a4
--- /dev/null
+++ b/doc/en/announce/release-8.3.5.rst
@@ -0,0 +1,26 @@
+pytest-8.3.5
+=======================================
+
+pytest 8.3.5 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement.
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Florian Bruhin
+* John Litborn
+* Kenny Y
+* Ran Benita
+* Sadra Barikbin
+* Vincent (Wen Yu) Ge
+* delta87
+* dongfangtianyu
+* mwychung
+* 🇺🇦 Sviatoslav Sydorenko (Святослав Сидоренко)
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/announce/sprint2016.rst b/doc/en/announce/sprint2016.rst
index 8e706589876..8d47a205c71 100644
--- a/doc/en/announce/sprint2016.rst
+++ b/doc/en/announce/sprint2016.rst
@@ -49,7 +49,7 @@ place on 20th, 21st, 22nd, 24th and 25th. On the 23rd we took a break
 day for some hot hiking in the Black Forest.
 
 Sprint activity was organised heavily around pairing, with plenty of group
-discusssions to take advantage of the high bandwidth, and lightning talks
+discussions to take advantage of the high bandwidth, and lightning talks
 as well.
 
 
diff --git a/doc/en/backwards-compatibility.rst b/doc/en/backwards-compatibility.rst
index 3a0ff126164..82f678b4dea 100644
--- a/doc/en/backwards-compatibility.rst
+++ b/doc/en/backwards-compatibility.rst
@@ -5,30 +5,26 @@ Backwards Compatibility Policy
 
 .. versionadded: 6.0
 
-pytest is actively evolving and is a project that has been decades in the making,
-we keep learning about new and better structures to express different details about testing.
+Pytest is an actively evolving project that has been decades in the making.
+We keep learning about new and better structures to express different details about testing.
 
-While we implement those modifications we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors.
+While we implement those modifications, we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors.
 
 As of now, pytest considers multiple types of backward compatibility transitions:
 
-a) trivial: APIs which trivially translate to the new mechanism,
-   and do not cause problematic changes.
+a) trivial: APIs that trivially translate to the new mechanism and do not cause problematic changes.
 
-   We try to support those indefinitely while encouraging users to switch to newer/better mechanisms through documentation.
+   We try to support those indefinitely while encouraging users to switch to newer or better mechanisms through documentation.
 
-b) transitional: the old and new API don't conflict
-   and we can help users transition by using warnings, while supporting both for a prolonged time.
+b) transitional: the old and new APIs don't conflict, and we can help users transition by using warnings while supporting both for a prolonged period of time.
 
-   We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
+   We will only start the removal of deprecated functionality in major releases (e.g., if we deprecate something in 3.0, we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g., if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
 
-   A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationwarning`).
+   A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationWarning`).
 
-   When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g. `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
+   When the deprecation expires (e.g., 4.0 is released), we won't remove the deprecated functionality immediately but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g., `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g., 4.1), the feature will be effectively removed.
 
-
-c) true breakage: should only be considered when normal transition is unreasonably unsustainable and would offset important development/features by years.
-   In addition, they should be limited to APIs where the number of actual users is very small (for example only impacting some plugins), and can be coordinated with the community in advance.
+c) True breakage should only be considered when a normal transition is unreasonably unsustainable and would offset important developments or features by years. In addition, they should be limited to APIs where the number of actual users is very small (for example, only impacting some plugins) and can be coordinated with the community in advance.
 
    Examples for such upcoming changes:
 
@@ -62,11 +58,11 @@ Focus primary on smooth transition - stance (pre 6.0)
 
 Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary.
 
-With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around.
+With the pytest 3.0 release, we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around.
 
-To communicate changes we issue deprecation warnings using a custom warning hierarchy (see :ref:`internal-warnings`). These warnings may be suppressed using the standard means: ``-W`` command-line flag or ``filterwarnings`` ini options (see :ref:`warnings`), but we suggest to use these sparingly and temporarily, and heed the warnings when possible.
+To communicate changes, we issue deprecation warnings using a custom warning hierarchy (see :ref:`internal-warnings`). These warnings may be suppressed using the standard means: ``-W`` command-line flag or ``filterwarnings`` ini options (see :ref:`warnings`), but we suggest to use these sparingly and temporarily, and heed the warnings when possible.
 
-We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
+We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0, we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
 
 When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
 
@@ -77,3 +73,22 @@ Deprecation Roadmap
 Features currently deprecated and removed in previous releases can be found in :ref:`deprecations`.
 
 We track future deprecation and removal of features using milestones and the `deprecation <https://github.com/pytest-dev/pytest/issues?q=label%3A%22type%3A+deprecation%22>`_ and `removal <https://github.com/pytest-dev/pytest/labels/type%3A%20removal>`_ labels on GitHub.
+
+
+Python version support
+======================
+
+Released pytest versions support all Python versions that are actively maintained at the time of the release:
+
+==============  ===================
+pytest version  min. Python version
+==============  ===================
+8.4+            3.9+
+8.0+            3.8+
+7.1+            3.7+
+6.2 - 7.0       3.6+
+5.0 - 6.1       3.5+
+3.3 - 4.6       2.7, 3.4+
+==============  ===================
+
+`Status of Python Versions <https://devguide.python.org/versions/>`__.
diff --git a/doc/en/broken-dep-constraints.txt b/doc/en/broken-dep-constraints.txt
new file mode 100644
index 00000000000..1488e06fa23
--- /dev/null
+++ b/doc/en/broken-dep-constraints.txt
@@ -0,0 +1,2 @@
+# This file contains transitive dependencies that need to be pinned for some reason.
+# Eventually this file will be empty, but in this case keep it around for future use.
diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst
index e22a874b4d5..8aa6fef681c 100644
--- a/doc/en/builtin.rst
+++ b/doc/en/builtin.rst
@@ -18,11 +18,11 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
 
     $ pytest  --fixtures -v
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collected 0 items
-    cache -- ../../../..$PYTHON_SITE/_pytest/cacheprovider.py:520
+    cache -- .../_pytest/cacheprovider.py:556
         Return a cache object that can persist state between testing sessions.
 
         cache.get(key, default)
@@ -33,49 +33,103 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
 
         Values can be any object handled by the json stdlib module.
 
-    capsys -- ../../../..$PYTHON_SITE/_pytest/capture.py:903
-        Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
-
-        The captured output is made available via ``capsys.readouterr()`` method
-        calls, which return a ``(out, err)`` namedtuple.
-        ``out`` and ``err`` will be ``text`` objects.
-
-    capsysbinary -- ../../../..$PYTHON_SITE/_pytest/capture.py:920
+    capsysbinary -- .../_pytest/capture.py:1024
         Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
 
         The captured output is made available via ``capsysbinary.readouterr()``
         method calls, which return a ``(out, err)`` namedtuple.
         ``out`` and ``err`` will be ``bytes`` objects.
 
-    capfd -- ../../../..$PYTHON_SITE/_pytest/capture.py:937
+        Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
+
+        Example:
+
+        .. code-block:: python
+
+            def test_output(capsysbinary):
+                print("hello")
+                captured = capsysbinary.readouterr()
+                assert captured.out == b"hello\n"
+
+    capfd -- .../_pytest/capture.py:1052
         Enable text capturing of writes to file descriptors ``1`` and ``2``.
 
         The captured output is made available via ``capfd.readouterr()`` method
         calls, which return a ``(out, err)`` namedtuple.
         ``out`` and ``err`` will be ``text`` objects.
 
-    capfdbinary -- ../../../..$PYTHON_SITE/_pytest/capture.py:954
+        Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
+
+        Example:
+
+        .. code-block:: python
+
+            def test_system_echo(capfd):
+                os.system('echo "hello"')
+                captured = capfd.readouterr()
+                assert captured.out == "hello\n"
+
+    capfdbinary -- .../_pytest/capture.py:1080
         Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
 
         The captured output is made available via ``capfd.readouterr()`` method
         calls, which return a ``(out, err)`` namedtuple.
         ``out`` and ``err`` will be ``byte`` objects.
 
-    doctest_namespace [session scope] -- ../../../..$PYTHON_SITE/_pytest/doctest.py:728
+        Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
+
+        Example:
+
+        .. code-block:: python
+
+            def test_system_echo(capfdbinary):
+                os.system('echo "hello"')
+                captured = capfdbinary.readouterr()
+                assert captured.out == b"hello\n"
+
+    capsys -- .../_pytest/capture.py:996
+        Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
+
+        The captured output is made available via ``capsys.readouterr()`` method
+        calls, which return a ``(out, err)`` namedtuple.
+        ``out`` and ``err`` will be ``text`` objects.
+
+        Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
+
+        Example:
+
+        .. code-block:: python
+
+            def test_output(capsys):
+                print("hello")
+                captured = capsys.readouterr()
+                assert captured.out == "hello\n"
+
+    doctest_namespace [session scope] -- .../_pytest/doctest.py:741
         Fixture that returns a :py:class:`dict` that will be injected into the
         namespace of doctests.
 
-    pytestconfig [session scope] -- ../../../..$PYTHON_SITE/_pytest/fixtures.py:1372
+        Usually this fixture is used in conjunction with another ``autouse`` fixture:
+
+        .. code-block:: python
+
+            @pytest.fixture(autouse=True)
+            def add_np(doctest_namespace):
+                doctest_namespace["np"] = numpy
+
+        For more details: :ref:`doctest_namespace`.
+
+    pytestconfig [session scope] -- .../_pytest/fixtures.py:1345
         Session-scoped fixture that returns the session's :class:`pytest.Config`
         object.
 
         Example::
 
             def test_foo(pytestconfig):
-                if pytestconfig.getoption("verbose") > 0:
+                if pytestconfig.get_verbosity() > 0:
                     ...
 
-    record_property -- ../../../..$PYTHON_SITE/_pytest/junitxml.py:282
+    record_property -- .../_pytest/junitxml.py:280
         Add extra properties to the calling test.
 
         User properties become part of the test report and are available to the
@@ -89,13 +143,13 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
             def test_function(record_property):
                 record_property("example_key", 1)
 
-    record_xml_attribute -- ../../../..$PYTHON_SITE/_pytest/junitxml.py:305
+    record_xml_attribute -- .../_pytest/junitxml.py:303
         Add extra xml attributes to the tag for the calling test.
 
         The fixture is callable with ``name, value``. The value is
         automatically XML-encoded.
 
-    record_testsuite_property [session scope] -- ../../../..$PYTHON_SITE/_pytest/junitxml.py:343
+    record_testsuite_property [session scope] -- .../_pytest/junitxml.py:341
         Record a new ``<property>`` tag as child of the root ``<testsuite>``.
 
         This is suitable to writing global information regarding the entire test
@@ -109,15 +163,35 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
                 record_testsuite_property("ARCH", "PPC")
                 record_testsuite_property("STORAGE_TYPE", "CEPH")
 
-        ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped.
+        :param name:
+            The property name.
+        :param value:
+            The property value. Will be converted to a string.
 
         .. warning::
 
             Currently this fixture **does not work** with the
-            `pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See issue
-            `#7767 <https://github.com/pytest-dev/pytest/issues/7767>`__ for details.
+            `pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See
+            :issue:`7767` for details.
+
+    tmpdir_factory [session scope] -- .../_pytest/legacypath.py:298
+        Return a :class:`pytest.TempdirFactory` instance for the test session.
+
+    tmpdir -- .../_pytest/legacypath.py:305
+        Return a temporary directory (as `legacy_path`_ object)
+        which is unique to each test function invocation.
+        The temporary directory is created as a subdirectory
+        of the base temporary directory, with configurable retention,
+        as discussed in :ref:`temporary directory location and retention`.
+
+        .. note::
+            These days, it is preferred to use ``tmp_path``.
 
-    caplog -- ../../../..$PYTHON_SITE/_pytest/logging.py:491
+            :ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`.
+
+        .. _legacy_path: https://py.readthedocs.io/en/latest/path.html
+
+    caplog -- .../_pytest/logging.py:598
         Access and control log capturing.
 
         Captured logs are available through the following properties/methods::
@@ -128,62 +202,44 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
         * caplog.record_tuples   -> list of (logger_name, level, message) tuples
         * caplog.clear()         -> clear captured records and formatted log output string
 
-    monkeypatch -- ../../../..$PYTHON_SITE/_pytest/monkeypatch.py:29
+    monkeypatch -- .../_pytest/monkeypatch.py:31
         A convenient fixture for monkey-patching.
 
-        The fixture provides these methods to modify objects, dictionaries or
-        os.environ::
+        The fixture provides these methods to modify objects, dictionaries, or
+        :data:`os.environ`:
 
-            monkeypatch.setattr(obj, name, value, raising=True)
-            monkeypatch.delattr(obj, name, raising=True)
-            monkeypatch.setitem(mapping, name, value)
-            monkeypatch.delitem(obj, name, raising=True)
-            monkeypatch.setenv(name, value, prepend=None)
-            monkeypatch.delenv(name, raising=True)
-            monkeypatch.syspath_prepend(path)
-            monkeypatch.chdir(path)
+        * :meth:`monkeypatch.setattr(obj, name, value, raising=True) <pytest.MonkeyPatch.setattr>`
+        * :meth:`monkeypatch.delattr(obj, name, raising=True) <pytest.MonkeyPatch.delattr>`
+        * :meth:`monkeypatch.setitem(mapping, name, value) <pytest.MonkeyPatch.setitem>`
+        * :meth:`monkeypatch.delitem(obj, name, raising=True) <pytest.MonkeyPatch.delitem>`
+        * :meth:`monkeypatch.setenv(name, value, prepend=None) <pytest.MonkeyPatch.setenv>`
+        * :meth:`monkeypatch.delenv(name, raising=True) <pytest.MonkeyPatch.delenv>`
+        * :meth:`monkeypatch.syspath_prepend(path) <pytest.MonkeyPatch.syspath_prepend>`
+        * :meth:`monkeypatch.chdir(path) <pytest.MonkeyPatch.chdir>`
+        * :meth:`monkeypatch.context() <pytest.MonkeyPatch.context>`
 
         All modifications will be undone after the requesting test function or
-        fixture has finished. The ``raising`` parameter determines if a KeyError
-        or AttributeError will be raised if the set/deletion operation has no target.
+        fixture has finished. The ``raising`` parameter determines if a :class:`KeyError`
+        or :class:`AttributeError` will be raised if the set/deletion operation does not have the
+        specified target.
 
-    recwarn -- ../../../..$PYTHON_SITE/_pytest/recwarn.py:29
-        Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
+        To undo modifications done by the fixture in a contained scope,
+        use :meth:`context() <pytest.MonkeyPatch.context>`.
 
-        See https://docs.python.org/library/how-to/capture-warnings.html for information
-        on warning categories.
+    recwarn -- .../_pytest/recwarn.py:35
+        Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
 
-    tmpdir_factory [session scope] -- ../../../..$PYTHON_SITE/_pytest/tmpdir.py:210
-        Return a :class:`pytest.TempdirFactory` instance for the test session.
+        See :ref:`warnings` for information on warning categories.
 
-    tmp_path_factory [session scope] -- ../../../..$PYTHON_SITE/_pytest/tmpdir.py:217
+    tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:241
         Return a :class:`pytest.TempPathFactory` instance for the test session.
 
-    tmpdir -- ../../../..$PYTHON_SITE/_pytest/tmpdir.py:232
-        Return a temporary directory path object which is unique to each test
-        function invocation, created as a sub directory of the base temporary
-        directory.
-
-        By default, a new base temporary directory is created each test session,
-        and old bases are removed after 3 sessions, to aid in debugging. If
-        ``--basetemp`` is used then it is cleared each session. See :ref:`base
-        temporary directory`.
-
-        The returned object is a `legacy_path`_ object.
-
-        .. _legacy_path: https://py.readthedocs.io/en/latest/path.html
-
-    tmp_path -- ../../../..$PYTHON_SITE/_pytest/tmpdir.py:250
-        Return a temporary directory path object which is unique to each test
-        function invocation, created as a sub directory of the base temporary
-        directory.
-
-        By default, a new base temporary directory is created each test session,
-        and old bases are removed after 3 sessions, to aid in debugging. If
-        ``--basetemp`` is used then it is cleared each session. See :ref:`base
-        temporary directory`.
-
-        The returned object is a :class:`pathlib.Path` object.
+    tmp_path -- .../_pytest/tmpdir.py:256
+        Return a temporary directory (as :class:`pathlib.Path` object)
+        which is unique to each test function invocation.
+        The temporary directory is created as a subdirectory
+        of the base temporary directory, with configurable retention,
+        as discussed in :ref:`temporary directory location and retention`.
 
 
     ========================== no tests ran in 0.12s ===========================
diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst
index 6cec2935d9a..c92cd7d4263 100644
--- a/doc/en/changelog.rst
+++ b/doc/en/changelog.rst
@@ -19,15 +19,2382 @@ with advance notice in the **Deprecations** section of releases.
     we named the news folder changelog
 
 
-.. only:: changelog_towncrier_draft
+.. only:: not is_release
 
-    .. The 'changelog_towncrier_draft' tag is included by our 'tox -e docs',
-       but not on readthedocs.
+   To be included in v\ |release| (if present)
+   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-    .. include:: _changelog_towncrier_draft.rst
+   .. towncrier-draft-entries:: |release| [UNRELEASED DRAFT]
+
+   Released versions
+   ^^^^^^^^^^^^^^^^^
 
 .. towncrier release notes start
 
+pytest 8.3.5 (2025-03-02)
+=========================
+
+Bug fixes
+---------
+
+- `#11777 <https://github.com/pytest-dev/pytest/issues/11777>`_: Fixed issue where sequences were still being shortened even with ``-vv`` verbosity.
+
+
+- `#12888 <https://github.com/pytest-dev/pytest/issues/12888>`_: Fixed broken input when using Python 3.13+ and a ``libedit`` build of Python, such as on macOS or with uv-managed Python binaries from the ``python-build-standalone`` project. This could manifest e.g. by a broken prompt when using ``Pdb``, or seeing empty inputs with manual usage of ``input()`` and suspended capturing.
+
+
+- `#13026 <https://github.com/pytest-dev/pytest/issues/13026>`_: Fixed :class:`AttributeError`  crash when using ``--import-mode=importlib`` when top-level directory same name as another module of the standard library.
+
+
+- `#13053 <https://github.com/pytest-dev/pytest/issues/13053>`_: Fixed a regression in pytest 8.3.4 where, when using ``--import-mode=importlib``, a directory containing py file with the same name would cause an ``ImportError``
+
+
+- `#13083 <https://github.com/pytest-dev/pytest/issues/13083>`_: Fixed issue where pytest could crash if one of the collected directories got removed during collection.
+
+
+
+Improved documentation
+----------------------
+
+- `#12842 <https://github.com/pytest-dev/pytest/issues/12842>`_: Added dedicated page about using types with pytest.
+
+  See :ref:`types` for detailed usage.
+
+
+
+Contributor-facing changes
+--------------------------
+
+- `#13112 <https://github.com/pytest-dev/pytest/issues/13112>`_: Fixed selftest failures in ``test_terminal.py`` with Pygments >= 2.19.0
+
+
+- `#13256 <https://github.com/pytest-dev/pytest/issues/13256>`_: Support for Towncrier versions released in 2024 has been re-enabled
+  when building Sphinx docs -- by :user:`webknjaz`.
+
+
+pytest 8.3.4 (2024-12-01)
+=========================
+
+Bug fixes
+---------
+
+- `#12592 <https://github.com/pytest-dev/pytest/issues/12592>`_: Fixed :class:`KeyError` crash when using ``--import-mode=importlib`` in a directory layout where a directory contains a child directory with the same name.
+
+
+- `#12818 <https://github.com/pytest-dev/pytest/issues/12818>`_: Assertion rewriting now preserves the source ranges of the original instructions, making it play well with tools that deal with the ``AST``, like `executing <https://github.com/alexmojaki/executing>`__.
+
+
+- `#12849 <https://github.com/pytest-dev/pytest/issues/12849>`_: ANSI escape codes for colored output now handled correctly in :func:`pytest.fail` with `pytrace=False`.
+
+
+- `#9353 <https://github.com/pytest-dev/pytest/issues/9353>`_: :func:`pytest.approx` now uses strict equality when given booleans.
+
+
+
+Improved documentation
+----------------------
+
+- `#10558 <https://github.com/pytest-dev/pytest/issues/10558>`_: Fix ambiguous docstring of :func:`pytest.Config.getoption`.
+
+
+- `#10829 <https://github.com/pytest-dev/pytest/issues/10829>`_: Improve documentation on the current handling of the ``--basetemp`` option and its lack of retention functionality (:ref:`temporary directory location and retention`).
+
+
+- `#12866 <https://github.com/pytest-dev/pytest/issues/12866>`_: Improved cross-references concerning the :fixture:`recwarn` fixture.
+
+
+- `#12966 <https://github.com/pytest-dev/pytest/issues/12966>`_: Clarify :ref:`filterwarnings` docs on filter precedence/order when using multiple :ref:`@pytest.mark.filterwarnings <pytest.mark.filterwarnings ref>` marks.
+
+
+
+Contributor-facing changes
+--------------------------
+
+- `#12497 <https://github.com/pytest-dev/pytest/issues/12497>`_: Fixed two failing pdb-related tests on Python 3.13.
+
+
+pytest 8.3.3 (2024-09-09)
+=========================
+
+Bug fixes
+---------
+
+- `#12446 <https://github.com/pytest-dev/pytest/issues/12446>`_: Avoid calling ``@property`` (and other instance descriptors) during fixture discovery -- by :user:`asottile`
+
+
+- `#12659 <https://github.com/pytest-dev/pytest/issues/12659>`_: Fixed the issue of not displaying assertion failure differences when using the parameter ``--import-mode=importlib`` in pytest>=8.1.
+
+
+- `#12667 <https://github.com/pytest-dev/pytest/issues/12667>`_: Fixed a regression where type change in `ExceptionInfo.errisinstance` caused `mypy` to fail.
+
+
+- `#12744 <https://github.com/pytest-dev/pytest/issues/12744>`_: Fixed typing compatibility with Python 3.9 or less -- replaced `typing.Self` with `typing_extensions.Self` -- by :user:`Avasam`
+
+
+- `#12745 <https://github.com/pytest-dev/pytest/issues/12745>`_: Fixed an issue with backslashes being incorrectly converted in nodeid paths on Windows, ensuring consistent path handling across environments.
+
+
+- `#6682 <https://github.com/pytest-dev/pytest/issues/6682>`_: Fixed bug where the verbosity levels where not being respected when printing the "msg" part of failed assertion (as in ``assert condition, msg``).
+
+
+- `#9422 <https://github.com/pytest-dev/pytest/issues/9422>`_: Fix bug where disabling the terminal plugin via ``-p no:terminal`` would cause crashes related to missing the ``verbose`` option.
+
+  -- by :user:`GTowers1`
+
+
+
+Improved documentation
+----------------------
+
+- `#12663 <https://github.com/pytest-dev/pytest/issues/12663>`_: Clarify that the `pytest_deselected` hook should be called from `pytest_collection_modifyitems` hook implementations when items are deselected.
+
+
+- `#12678 <https://github.com/pytest-dev/pytest/issues/12678>`_: Remove erroneous quotes from `tmp_path_retention_policy` example in docs.
+
+
+
+Miscellaneous internal changes
+------------------------------
+
+- `#12769 <https://github.com/pytest-dev/pytest/issues/12769>`_: Fix typos discovered by codespell and add codespell to pre-commit hooks.
+
+
+pytest 8.3.2 (2024-07-24)
+=========================
+
+Bug fixes
+---------
+
+- `#12652 <https://github.com/pytest-dev/pytest/issues/12652>`_: Resolve regression `conda` environments where no longer being automatically detected.
+
+  -- by :user:`RonnyPfannschmidt`
+
+
+pytest 8.3.1 (2024-07-20)
+=========================
+
+The 8.3.0 release failed to include the change notes and docs for the release. This patch release remedies this. There are no other changes.
+
+
+pytest 8.3.0 (2024-07-20)
+=========================
+
+New features
+------------
+
+- `#12231 <https://github.com/pytest-dev/pytest/issues/12231>`_: Added `--xfail-tb` flag, which turns on traceback output for XFAIL results.
+
+  * If the `--xfail-tb` flag is not given, tracebacks for XFAIL results are NOT shown.
+  * The style of traceback for XFAIL is set with `--tb`, and can be `auto|long|short|line|native|no`.
+  * Note: Even if you have `--xfail-tb` set, you won't see them if `--tb=no`.
+
+  Some history:
+
+  With pytest 8.0, `-rx` or `-ra` would not only turn on summary reports for xfail, but also report the tracebacks for xfail results. This caused issues with some projects that utilize xfail, but don't want to see all of the xfail tracebacks.
+
+  This change detaches xfail tracebacks from `-rx`, and now we turn on xfail tracebacks with `--xfail-tb`. With this, the default `-rx`/ `-ra` behavior is identical to pre-8.0 with respect to xfail tracebacks. While this is a behavior change, it brings default behavior back to pre-8.0.0 behavior, which ultimately was considered the better course of action.
+
+  -- by :user:`okken`
+
+
+- `#12281 <https://github.com/pytest-dev/pytest/issues/12281>`_: Added support for keyword matching in marker expressions.
+
+  Now tests can be selected by marker keyword arguments.
+  Supported values are :class:`int`, (unescaped) :class:`str`, :class:`bool` & :data:`None`.
+
+  See :ref:`marker examples <marker_keyword_expression_example>` for more information.
+
+  -- by :user:`lovetheguitar`
+
+
+- `#12567 <https://github.com/pytest-dev/pytest/issues/12567>`_: Added ``--no-fold-skipped`` command line option.
+
+  If this option is set, then skipped tests in short summary are no longer grouped
+  by reason but all tests are printed individually with their nodeid in the same
+  way as other statuses.
+
+  -- by :user:`pbrezina`
+
+
+
+Improvements in existing functionality
+--------------------------------------
+
+- `#12469 <https://github.com/pytest-dev/pytest/issues/12469>`_: The console output now uses the "third-party plugins" terminology,
+  replacing the previously established but confusing and outdated
+  reference to :std:doc:`setuptools <setuptools:index>`
+  -- by :user:`webknjaz`.
+
+
+- `#12544 <https://github.com/pytest-dev/pytest/issues/12544>`_, `#12545 <https://github.com/pytest-dev/pytest/issues/12545>`_: Python virtual environment detection was improved by
+  checking for a :file:`pyvenv.cfg` file, ensuring reliable detection on
+  various platforms -- by :user:`zachsnickers`.
+
+
+- `#2871 <https://github.com/pytest-dev/pytest/issues/2871>`_: Do not truncate arguments to functions in output when running with `-vvv`.
+
+
+- `#389 <https://github.com/pytest-dev/pytest/issues/389>`_: The readability of assertion introspection of bound methods has been enhanced
+  -- by :user:`farbodahm`, :user:`webknjaz`, :user:`obestwalter`, :user:`flub`
+  and :user:`glyphack`.
+
+  Earlier, it was like:
+
+  .. code-block:: console
+
+      =================================== FAILURES ===================================
+      _____________________________________ test _____________________________________
+
+          def test():
+      >       assert Help().fun() == 2
+      E       assert 1 == 2
+      E        +  where 1 = <bound method Help.fun of <example.Help instance at 0x256a830>>()
+      E        +    where <bound method Help.fun of <example.Help instance at 0x256a830>> = <example.Help instance at 0x256a830>.fun
+      E        +      where <example.Help instance at 0x256a830> = Help()
+
+      example.py:7: AssertionError
+      =========================== 1 failed in 0.03 seconds ===========================
+
+
+  And now it's like:
+
+  .. code-block:: console
+
+      =================================== FAILURES ===================================
+      _____________________________________ test _____________________________________
+
+          def test():
+      >       assert Help().fun() == 2
+      E       assert 1 == 2
+      E        +  where 1 = fun()
+      E        +    where fun = <test_local.Help object at 0x1074be230>.fun
+      E        +      where <test_local.Help object at 0x1074be230> = Help()
+
+      test_local.py:13: AssertionError
+      =========================== 1 failed in 0.03 seconds ===========================
+
+
+- `#7662 <https://github.com/pytest-dev/pytest/issues/7662>`_: Added timezone information to the testsuite timestamp in the JUnit XML report.
+
+
+
+Bug fixes
+---------
+
+- `#11706 <https://github.com/pytest-dev/pytest/issues/11706>`_: Fixed reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`.
+
+  Originally added in pytest 8.0.0, but reverted in 8.0.2 due to a regression in pytest-xdist.
+  This regression was fixed in pytest-xdist 3.6.1.
+
+
+- `#11797 <https://github.com/pytest-dev/pytest/issues/11797>`_: :func:`pytest.approx` now correctly handles :class:`Sequence <collections.abc.Sequence>`-like objects.
+
+
+- `#12204 <https://github.com/pytest-dev/pytest/issues/12204>`_, `#12264 <https://github.com/pytest-dev/pytest/issues/12264>`_: Fixed a regression in pytest 8.0 where tracebacks get longer and longer when multiple
+  tests fail due to a shared higher-scope fixture which raised -- by :user:`bluetech`.
+
+  Also fixed a similar regression in pytest 5.4 for collectors which raise during setup.
+
+  The fix necessitated internal changes which may affect some plugins:
+
+  * ``FixtureDef.cached_result[2]`` is now a tuple ``(exc, tb)``
+    instead of ``exc``.
+  * ``SetupState.stack`` failures are now a tuple ``(exc, tb)``
+    instead of ``exc``.
+
+
+- `#12275 <https://github.com/pytest-dev/pytest/issues/12275>`_: Fixed collection error upon encountering an :mod:`abstract <abc>` class, including abstract `unittest.TestCase` subclasses.
+
+
+- `#12328 <https://github.com/pytest-dev/pytest/issues/12328>`_: Fixed a regression in pytest 8.0.0 where package-scoped parameterized items were not correctly reordered to minimize setups/teardowns in some cases.
+
+
+- `#12424 <https://github.com/pytest-dev/pytest/issues/12424>`_: Fixed crash with `assert testcase is not None` assertion failure when re-running unittest tests using plugins like pytest-rerunfailures. Regressed in 8.2.2.
+
+
+- `#12472 <https://github.com/pytest-dev/pytest/issues/12472>`_: Fixed a crash when returning category ``"error"`` or ``"failed"`` with a custom test status from :hook:`pytest_report_teststatus` hook -- :user:`pbrezina`.
+
+
+- `#12505 <https://github.com/pytest-dev/pytest/issues/12505>`_: Improved handling of invalid regex patterns in :func:`pytest.raises(match=r'...') <pytest.raises>` by providing a clear error message.
+
+
+- `#12580 <https://github.com/pytest-dev/pytest/issues/12580>`_: Fixed a crash when using the cache class on Windows and the cache directory was created concurrently.
+
+
+- `#6962 <https://github.com/pytest-dev/pytest/issues/6962>`_: Parametrization parameters are now compared using `==` instead of `is` (`is` is still used as a fallback if the parameter does not support `==`).
+  This fixes use of parameters such as lists, which have a different `id` but compare equal, causing fixtures to be re-computed instead of being cached.
+
+
+- `#7166 <https://github.com/pytest-dev/pytest/issues/7166>`_: Fixed progress percentages (the ``[ 87%]`` at the edge of the screen) sometimes not aligning correctly when running with pytest-xdist ``-n``.
+
+
+
+Improved documentation
+----------------------
+
+- `#12153 <https://github.com/pytest-dev/pytest/issues/12153>`_: Documented using :envvar:`PYTEST_VERSION` to detect if code is running from within a pytest run.
+
+
+- `#12469 <https://github.com/pytest-dev/pytest/issues/12469>`_: The external plugin mentions in the documentation now avoid mentioning
+  :std:doc:`setuptools entry-points <setuptools:index>` as the concept is
+  much more generic nowadays. Instead, the terminology of "external",
+  "installed", or "third-party" plugins (or packages) replaces that.
+
+  -- by :user:`webknjaz`
+
+
+- `#12577 <https://github.com/pytest-dev/pytest/issues/12577>`_: `CI` and `BUILD_NUMBER` environment variables role is described in
+  the reference doc. They now also appear when doing `pytest -h`
+  -- by :user:`MarcBresson`.
+
+
+
+Contributor-facing changes
+--------------------------
+
+- `#12467 <https://github.com/pytest-dev/pytest/issues/12467>`_: Migrated all internal type-annotations to the python3.10+ style by using the `annotations` future import.
+
+  -- by :user:`RonnyPfannschmidt`
+
+
+- `#11771 <https://github.com/pytest-dev/pytest/issues/11771>`_, `#12557 <https://github.com/pytest-dev/pytest/issues/12557>`_: The PyPy runtime version has been updated to 3.9 from 3.8 that introduced
+  a flaky bug at the garbage collector which was not expected to fix there
+  as the 3.8 is EoL.
+
+  -- by :user:`x612skm`
+
+
+- `#12493 <https://github.com/pytest-dev/pytest/issues/12493>`_: The change log draft preview integration has been refactored to use a
+  third party extension ``sphinxcontib-towncrier``. The previous in-repo
+  script was putting the change log preview file at
+  :file:`doc/en/_changelog_towncrier_draft.rst`. Said file is no longer
+  ignored in Git and might show up among untracked files in the
+  development environments of the contributors. To address that, the
+  contributors can run the following command that will clean it up:
+
+  .. code-block:: console
+
+     $ git clean -x -i -- doc/en/_changelog_towncrier_draft.rst
+
+  -- by :user:`webknjaz`
+
+
+- `#12498 <https://github.com/pytest-dev/pytest/issues/12498>`_: All the undocumented ``tox`` environments now have descriptions.
+  They can be listed in one's development environment by invoking
+  ``tox -av`` in a terminal.
+
+  -- by :user:`webknjaz`
+
+
+- `#12501 <https://github.com/pytest-dev/pytest/issues/12501>`_: The changelog configuration has been updated to introduce more accurate
+  audience-tailored categories. Previously, there was a ``trivial``
+  change log fragment type with an unclear and broad meaning. It was
+  removed and we now have ``contrib``, ``misc`` and ``packaging`` in
+  place of it.
+
+  The new change note types target the readers who are downstream
+  packagers and project contributors. Additionally, the miscellaneous
+  section is kept for unspecified updates that do not fit anywhere else.
+
+  -- by :user:`webknjaz`
+
+
+- `#12502 <https://github.com/pytest-dev/pytest/issues/12502>`_: The UX of the GitHub automation making pull requests to update the
+  plugin list has been updated. Previously, the maintainers had to close
+  the automatically created pull requests and re-open them to trigger the
+  CI runs. From now on, they only need to click the `Ready for review`
+  button instead.
+
+  -- by :user:`webknjaz`
+
+
+- `#12522 <https://github.com/pytest-dev/pytest/issues/12522>`_: The ``:pull:`` RST role has been replaced with a shorter
+  ``:pr:`` due to starting to use the implementation from
+  the third-party :pypi:`sphinx-issues` Sphinx extension
+  -- by :user:`webknjaz`.
+
+
+- `#12531 <https://github.com/pytest-dev/pytest/issues/12531>`_: The coverage reporting configuration has been updated to exclude
+  pytest's own tests marked as expected to fail from the coverage
+  report. This has an effect of reducing the influence of flaky
+  tests on the resulting number.
+
+  -- by :user:`webknjaz`
+
+
+- `#12533 <https://github.com/pytest-dev/pytest/issues/12533>`_: The ``extlinks`` Sphinx extension is no longer enabled. The ``:bpo:``
+  role it used to declare has been removed with that. BPO itself has
+  migrated to GitHub some years ago and it is possible to link the
+  respective issues by using their GitHub issue numbers and the
+  ``:issue:`` role that the ``sphinx-issues`` extension implements.
+
+  -- by :user:`webknjaz`
+
+
+- `#12562 <https://github.com/pytest-dev/pytest/issues/12562>`_: Possible typos in using the ``:user:`` RST role is now being linted
+  through the pre-commit tool integration -- by :user:`webknjaz`.
+
+
+pytest 8.2.2 (2024-06-04)
+=========================
+
+Bug Fixes
+---------
+
+- `#12355 <https://github.com/pytest-dev/pytest/issues/12355>`_: Fix possible catastrophic performance slowdown on a certain parametrization pattern involving many higher-scoped parameters.
+
+
+- `#12367 <https://github.com/pytest-dev/pytest/issues/12367>`_: Fix a regression in pytest 8.2.0 where unittest class instances (a fresh one is created for each test) were not released promptly on test teardown but only on session teardown.
+
+
+- `#12381 <https://github.com/pytest-dev/pytest/issues/12381>`_: Fix possible "Directory not empty" crashes arising from concurrent cache dir (``.pytest_cache``) creation. Regressed in pytest 8.2.0.
+
+
+
+Improved Documentation
+----------------------
+
+- `#12290 <https://github.com/pytest-dev/pytest/issues/12290>`_: Updated Sphinx theme to use Furo instead of Flask, enabling Dark mode theme.
+
+
+- `#12356 <https://github.com/pytest-dev/pytest/issues/12356>`_: Added a subsection to the documentation for debugging flaky tests to mention
+  lack of thread safety in pytest as a possible source of flakiness.
+
+
+- `#12363 <https://github.com/pytest-dev/pytest/issues/12363>`_: The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results.
+
+
+pytest 8.2.1 (2024-05-19)
+=========================
+
+Improvements
+------------
+
+- `#12334 <https://github.com/pytest-dev/pytest/issues/12334>`_: Support for Python 3.13 (beta1 at the time of writing).
+
+
+
+Bug Fixes
+---------
+
+- `#12120 <https://github.com/pytest-dev/pytest/issues/12120>`_: Fix `PermissionError` crashes arising from directories which are not selected on the command-line.
+
+
+- `#12191 <https://github.com/pytest-dev/pytest/issues/12191>`_: Keyboard interrupts and system exits are now properly handled during the test collection.
+
+
+- `#12300 <https://github.com/pytest-dev/pytest/issues/12300>`_: Fixed handling of 'Function not implemented' error under squashfuse_ll, which is a different way to say that the mountpoint is read-only.
+
+
+- `#12308 <https://github.com/pytest-dev/pytest/issues/12308>`_: Fix a regression in pytest 8.2.0 where the permissions of automatically-created ``.pytest_cache`` directories became ``rwx------`` instead of the expected ``rwxr-xr-x``.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#12333 <https://github.com/pytest-dev/pytest/issues/12333>`_: pytest releases are now attested using the recent `Artifact Attestation <https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/>`_ support from GitHub, allowing users to verify the provenance of pytest's sdist and wheel artifacts.
+
+
+pytest 8.2.0 (2024-04-27)
+=========================
+
+Breaking Changes
+----------------
+
+- `#12089 <https://github.com/pytest-dev/pytest/pull/12089>`_: pytest now requires that :class:`unittest.TestCase` subclasses can be instantiated freely using ``MyTestCase('runTest')``.
+
+  If the class doesn't allow this, you may see an error during collection such as ``AttributeError: 'MyTestCase' object has no attribute 'runTest'``.
+
+  Classes which do not override ``__init__``, or do not access the test method in ``__init__`` using ``getattr`` or similar, are unaffected.
+
+  Classes which do should take care to not crash when ``"runTest"`` is given, as is shown in `unittest.TestCases's implementation <https://github.com/python/cpython/blob/51aefc5bf907ddffaaf083ded0de773adcdf08c8/Lib/unittest/case.py#L419-L426>`_.
+  Alternatively, consider using :meth:`setUp <unittest.TestCase.setUp>` instead of ``__init__``.
+
+  If you run into this issue using ``tornado.AsyncTestCase``, please see `issue 12263 <https://github.com/pytest-dev/pytest/issues/12263>`_.
+
+  If you run into this issue using an abstract ``TestCase`` subclass, please see `issue 12275 <https://github.com/pytest-dev/pytest/issues/12275>`_.
+
+  Historical note: the effect of this change on custom TestCase implementations was not properly considered initially, this is why it was done in a minor release. We apologize for the inconvenience.
+
+Deprecations
+------------
+
+- `#12069 <https://github.com/pytest-dev/pytest/issues/12069>`_: A deprecation warning is now raised when implementations of one of the following hooks request a deprecated ``py.path.local`` parameter instead of the ``pathlib.Path`` parameter which replaced it:
+
+  - :hook:`pytest_ignore_collect` - the ``path`` parameter - use ``collection_path`` instead.
+  - :hook:`pytest_collect_file` - the ``path`` parameter - use ``file_path`` instead.
+  - :hook:`pytest_pycollect_makemodule` - the ``path`` parameter - use ``module_path`` instead.
+  - :hook:`pytest_report_header` - the ``startdir`` parameter - use ``start_path`` instead.
+  - :hook:`pytest_report_collectionfinish` - the ``startdir`` parameter - use ``start_path`` instead.
+
+  The replacement parameters are available since pytest 7.0.0.
+  The old parameters will be removed in pytest 9.0.0.
+
+  See :ref:`legacy-path-hooks-deprecated` for more details.
+
+
+
+Features
+--------
+
+- `#11871 <https://github.com/pytest-dev/pytest/issues/11871>`_: Added support for reading command line arguments from a file using the prefix character ``@``, like e.g.: ``pytest @tests.txt``. The file must have one argument per line.
+
+  See :ref:`Read arguments from file <args-from-file>` for details.
+
+
+
+Improvements
+------------
+
+- `#11523 <https://github.com/pytest-dev/pytest/issues/11523>`_: :func:`pytest.importorskip` will now issue a warning if the module could be found, but raised :class:`ImportError` instead of :class:`ModuleNotFoundError`.
+
+  The warning can be suppressed by passing ``exc_type=ImportError`` to :func:`pytest.importorskip`.
+
+  See :ref:`import-or-skip-import-error` for details.
+
+
+- `#11728 <https://github.com/pytest-dev/pytest/issues/11728>`_: For ``unittest``-based tests, exceptions during class cleanup (as raised by functions registered with :meth:`TestCase.addClassCleanup <unittest.TestCase.addClassCleanup>`) are now reported instead of silently failing.
+
+
+- `#11777 <https://github.com/pytest-dev/pytest/issues/11777>`_: Text is no longer truncated in the ``short test summary info`` section when ``-vv`` is given.
+
+
+- `#12112 <https://github.com/pytest-dev/pytest/issues/12112>`_: Improved namespace packages detection when :confval:`consider_namespace_packages` is enabled, covering more situations (like editable installs).
+
+
+- `#9502 <https://github.com/pytest-dev/pytest/issues/9502>`_: Added :envvar:`PYTEST_VERSION` environment variable which is defined at the start of the pytest session and undefined afterwards. It contains the value of ``pytest.__version__``, and among other things can be used to easily check if code is running from within a pytest run.
+
+
+
+Bug Fixes
+---------
+
+- `#12065 <https://github.com/pytest-dev/pytest/issues/12065>`_: Fixed a regression in pytest 8.0.0 where test classes containing ``setup_method`` and tests using ``@staticmethod`` or ``@classmethod`` would crash with ``AttributeError: 'NoneType' object has no attribute 'setup_method'``.
+
+  Now the :attr:`request.instance <pytest.FixtureRequest.instance>` attribute of tests using ``@staticmethod`` and ``@classmethod`` is no longer ``None``, but a fresh instance of the class, like in non-static methods.
+  Previously it was ``None``, and all fixtures of such tests would share a single ``self``.
+
+
+- `#12135 <https://github.com/pytest-dev/pytest/issues/12135>`_: Fixed issue where fixtures adding their finalizer multiple times to fixtures they request would cause unreliable and non-intuitive teardown ordering in some instances.
+
+
+- `#12194 <https://github.com/pytest-dev/pytest/issues/12194>`_: Fixed a bug with ``--importmode=importlib`` and ``--doctest-modules`` where child modules did not appear as attributes in parent modules.
+
+
+- `#1489 <https://github.com/pytest-dev/pytest/issues/1489>`_: Fixed some instances where teardown of higher-scoped fixtures was not happening in the reverse order they were initialized in.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#12069 <https://github.com/pytest-dev/pytest/issues/12069>`_: ``pluggy>=1.5.0`` is now required.
+
+
+- `#12167 <https://github.com/pytest-dev/pytest/issues/12167>`_: :ref:`cache <cache>`: create supporting files (``CACHEDIR.TAG``, ``.gitignore``, etc.) in a temporary directory to provide atomic semantics.
+
+
+pytest 8.1.2 (2024-04-26)
+=========================
+
+Bug Fixes
+---------
+
+- `#12114 <https://github.com/pytest-dev/pytest/issues/12114>`_: Fixed error in :func:`pytest.approx` when used with `numpy` arrays and comparing with other types.
+
+
+pytest 8.1.1 (2024-03-08)
+=========================
+
+.. note::
+
+       This release is not a usual bug fix release -- it contains features and improvements, being a follow up
+       to ``8.1.0``, which has been yanked from PyPI.
+
+Features
+--------
+
+- `#11475 <https://github.com/pytest-dev/pytest/issues/11475>`_: Added the new :confval:`consider_namespace_packages` configuration option, defaulting to ``False``.
+
+  If set to ``True``, pytest will attempt to identify modules that are part of `namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages>`__ when importing modules.
+
+
+- `#11653 <https://github.com/pytest-dev/pytest/issues/11653>`_: Added the new :confval:`verbosity_test_cases` configuration option for fine-grained control of test execution verbosity.
+  See :ref:`Fine-grained verbosity <pytest.fine_grained_verbosity>` for more details.
+
+
+
+Improvements
+------------
+
+- `#10865 <https://github.com/pytest-dev/pytest/issues/10865>`_: :func:`pytest.warns` now validates that :func:`warnings.warn` was called with a `str` or a `Warning`.
+  Currently in Python it is possible to use other types, however this causes an exception when :func:`warnings.filterwarnings` is used to filter those warnings (see `CPython #103577 <https://github.com/python/cpython/issues/103577>`__ for a discussion).
+  While this can be considered a bug in CPython, we decided to put guards in pytest as the error message produced without this check in place is confusing.
+
+
+- `#11311 <https://github.com/pytest-dev/pytest/issues/11311>`_: When using ``--override-ini`` for paths in invocations without a configuration file defined, the current working directory is used
+  as the relative directory.
+
+  Previously this would raise an :class:`AssertionError`.
+
+
+- `#11475 <https://github.com/pytest-dev/pytest/issues/11475>`_: :ref:`--import-mode=importlib <import-mode-importlib>` now tries to import modules using the standard import mechanism (but still without changing :py:data:`sys.path`), falling back to importing modules directly only if that fails.
+
+  This means that installed packages will be imported under their canonical name if possible first, for example ``app.core.models``, instead of having the module name always be derived from their path (for example ``.env310.lib.site_packages.app.core.models``).
+
+
+- `#11801 <https://github.com/pytest-dev/pytest/issues/11801>`_: Added the :func:`iter_parents() <_pytest.nodes.Node.iter_parents>` helper method on nodes.
+  It is similar to :func:`listchain <_pytest.nodes.Node.listchain>`, but goes from bottom to top, and returns an iterator, not a list.
+
+
+- `#11850 <https://github.com/pytest-dev/pytest/issues/11850>`_: Added support for :data:`sys.last_exc` for post-mortem debugging on Python>=3.12.
+
+
+- `#11962 <https://github.com/pytest-dev/pytest/issues/11962>`_: In case no other suitable candidates for configuration file are found, a ``pyproject.toml`` (even without a ``[tool.pytest.ini_options]`` table) will be considered as the configuration file and define the ``rootdir``.
+
+
+- `#11978 <https://github.com/pytest-dev/pytest/issues/11978>`_: Add ``--log-file-mode`` option to the logging plugin, enabling appending to log-files. This option accepts either ``"w"`` or ``"a"`` and defaults to ``"w"``.
+
+  Previously, the mode was hard-coded to be ``"w"`` which truncates the file before logging.
+
+
+- `#12047 <https://github.com/pytest-dev/pytest/issues/12047>`_: When multiple finalizers of a fixture raise an exception, now all exceptions are reported as an exception group.
+  Previously, only the first exception was reported.
+
+
+
+Bug Fixes
+---------
+
+- `#11475 <https://github.com/pytest-dev/pytest/issues/11475>`_: Fixed regression where ``--importmode=importlib`` would import non-test modules more than once.
+
+
+- `#11904 <https://github.com/pytest-dev/pytest/issues/11904>`_: Fixed a regression in pytest 8.0.0 that would cause test collection to fail due to permission errors when using ``--pyargs``.
+
+  This change improves the collection tree for tests specified using ``--pyargs``, see :pr:`12043` for a comparison with pytest 8.0 and <8.
+
+
+- `#12011 <https://github.com/pytest-dev/pytest/issues/12011>`_: Fixed a regression in 8.0.1 whereby ``setup_module`` xunit-style fixtures are not executed when ``--doctest-modules`` is passed.
+
+
+- `#12014 <https://github.com/pytest-dev/pytest/issues/12014>`_: Fix the ``stacklevel`` used when warning about marks used on fixtures.
+
+
+- `#12039 <https://github.com/pytest-dev/pytest/issues/12039>`_: Fixed a regression in ``8.0.2`` where tests created using :fixture:`tmp_path` have been collected multiple times in CI under Windows.
+
+
+Improved Documentation
+----------------------
+
+- `#11790 <https://github.com/pytest-dev/pytest/issues/11790>`_: Documented the retention of temporary directories created using the ``tmp_path`` fixture in more detail.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#11785 <https://github.com/pytest-dev/pytest/issues/11785>`_: Some changes were made to private functions which may affect plugins which access them:
+
+  - ``FixtureManager._getautousenames()`` now takes a ``Node`` itself instead of the nodeid.
+  - ``FixtureManager.getfixturedefs()`` now takes the ``Node`` itself instead of the nodeid.
+  - The ``_pytest.nodes.iterparentnodeids()`` function is removed without replacement.
+    Prefer to traverse the node hierarchy itself instead.
+    If you really need to, copy the function from the previous pytest release.
+
+
+- `#12069 <https://github.com/pytest-dev/pytest/issues/12069>`_: Delayed the deprecation of the following features to ``9.0.0``:
+
+  * :ref:`node-ctor-fspath-deprecation`.
+  * :ref:`legacy-path-hooks-deprecated`.
+
+  It was discovered after ``8.1.0`` was released that the warnings about the impeding removal were not being displayed, so the team decided to revert the removal.
+
+  This is the reason for ``8.1.0`` being yanked.
+
+
+pytest 8.1.0 (YANKED)
+=====================
+
+
+.. note::
+
+       This release has been **yanked**: it broke some plugins without the proper warning period, due to
+       some warnings not showing up as expected.
+
+       See `#12069 <https://github.com/pytest-dev/pytest/issues/12069>`__.
+
+
+pytest 8.0.2 (2024-02-24)
+=========================
+
+Bug Fixes
+---------
+
+- `#11895 <https://github.com/pytest-dev/pytest/issues/11895>`_: Fix collection on Windows where initial paths contain the short version of a path (for example ``c:\PROGRA~1\tests``).
+
+
+- `#11953 <https://github.com/pytest-dev/pytest/issues/11953>`_: Fix an ``IndexError`` crash raising from ``getstatementrange_ast``.
+
+
+- `#12021 <https://github.com/pytest-dev/pytest/issues/12021>`_: Reverted a fix to `--maxfail` handling in pytest 8.0.0 because it caused a regression in pytest-xdist whereby session fixture teardowns may get executed multiple times when the max-fails is reached.
+
+
+pytest 8.0.1 (2024-02-16)
+=========================
+
+Bug Fixes
+---------
+
+- `#11875 <https://github.com/pytest-dev/pytest/issues/11875>`_: Correctly handle errors from :func:`getpass.getuser` in Python 3.13.
+
+
+- `#11879 <https://github.com/pytest-dev/pytest/issues/11879>`_: Fix an edge case where ``ExceptionInfo._stringify_exception`` could crash :func:`pytest.raises`.
+
+
+- `#11906 <https://github.com/pytest-dev/pytest/issues/11906>`_: Fix regression with :func:`pytest.warns` using custom warning subclasses which have more than one parameter in their `__init__`.
+
+
+- `#11907 <https://github.com/pytest-dev/pytest/issues/11907>`_: Fix a regression in pytest 8.0.0 whereby calling :func:`pytest.skip` and similar control-flow exceptions within a :func:`pytest.warns()` block would get suppressed instead of propagating.
+
+
+- `#11929 <https://github.com/pytest-dev/pytest/issues/11929>`_: Fix a regression in pytest 8.0.0 whereby autouse fixtures defined in a module get ignored by the doctests in the module.
+
+
+- `#11937 <https://github.com/pytest-dev/pytest/issues/11937>`_: Fix a regression in pytest 8.0.0 whereby items would be collected in reverse order in some circumstances.
+
+
+pytest 8.0.0 (2024-01-27)
+=========================
+
+Bug Fixes
+---------
+
+- `#11842 <https://github.com/pytest-dev/pytest/issues/11842>`_: Properly escape the ``reason`` of a :ref:`skip <pytest.mark.skip ref>` mark when writing JUnit XML files.
+
+
+- `#11861 <https://github.com/pytest-dev/pytest/issues/11861>`_: Avoid microsecond exceeds ``1_000_000`` when using ``log-date-format`` with ``%f`` specifier, which might cause the test suite to crash.
+
+
+pytest 8.0.0rc2 (2024-01-17)
+============================
+
+
+Improvements
+------------
+
+- `#11233 <https://github.com/pytest-dev/pytest/issues/11233>`_: Improvements to ``-r`` for xfailures and xpasses:
+
+  * Report tracebacks for xfailures when ``-rx`` is set.
+  * Report captured output for xpasses when ``-rX`` is set.
+  * For xpasses, add ``-`` in summary between test name and reason, to match how xfail is displayed.
+
+- `#11825 <https://github.com/pytest-dev/pytest/issues/11825>`_: The :hook:`pytest_plugin_registered` hook has a new ``plugin_name`` parameter containing the name by which ``plugin`` is registered.
+
+
+Bug Fixes
+---------
+
+- `#11706 <https://github.com/pytest-dev/pytest/issues/11706>`_: Fix reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`.
+
+  NOTE: This change was reverted in pytest 8.0.2 to fix a `regression <https://github.com/pytest-dev/pytest-xdist/issues/1024>`_ it caused in pytest-xdist.
+
+
+- `#11758 <https://github.com/pytest-dev/pytest/issues/11758>`_: Fixed ``IndexError: string index out of range`` crash in ``if highlighted[-1] == "\n" and source[-1] != "\n"``.
+  This bug was introduced in pytest 8.0.0rc1.
+
+
+- `#9765 <https://github.com/pytest-dev/pytest/issues/9765>`_, `#11816 <https://github.com/pytest-dev/pytest/issues/11816>`_: Fixed a frustrating bug that afflicted some users with the only error being ``assert mod not in mods``. The issue was caused by the fact that ``str(Path(mod))`` and ``mod.__file__`` don't necessarily produce the same string, and was being erroneously used interchangeably in some places in the code.
+
+  This fix also broke the internal API of ``PytestPluginManager.consider_conftest`` by introducing a new parameter -- we mention this in case it is being used by external code, even if marked as *private*.
+
+
+pytest 8.0.0rc1 (2023-12-30)
+============================
+
+Breaking Changes
+----------------
+
+Old Deprecations Are Now Errors
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- `#7363 <https://github.com/pytest-dev/pytest/issues/7363>`_: **PytestRemovedIn8Warning deprecation warnings are now errors by default.**
+
+  Following our plan to remove deprecated features with as little disruption as
+  possible, all warnings of type ``PytestRemovedIn8Warning`` now generate errors
+  instead of warning messages by default.
+
+  **The affected features will be effectively removed in pytest 8.1**, so please consult the
+  :ref:`deprecations` section in the docs for directions on how to update existing code.
+
+  In the pytest ``8.0.X`` series, it is possible to change the errors back into warnings as a
+  stopgap measure by adding this to your ``pytest.ini`` file:
+
+  .. code-block:: ini
+
+      [pytest]
+      filterwarnings =
+          ignore::pytest.PytestRemovedIn8Warning
+
+  But this will stop working when pytest ``8.1`` is released.
+
+  **If you have concerns** about the removal of a specific feature, please add a
+  comment to :issue:`7363`.
+
+
+Version Compatibility
+^^^^^^^^^^^^^^^^^^^^^
+
+- `#11151 <https://github.com/pytest-dev/pytest/issues/11151>`_: Dropped support for Python 3.7, which `reached end-of-life on 2023-06-27 <https://devguide.python.org/versions/>`__.
+
+
+- ``pluggy>=1.3.0`` is now required.
+
+
+Collection Changes
+^^^^^^^^^^^^^^^^^^
+
+In this version we've made several breaking changes to pytest's collection phase,
+particularly around how filesystem directories and Python packages are collected,
+fixing deficiencies and allowing for cleanups and improvements to pytest's internals.
+A deprecation period for these changes was not possible.
+
+
+- `#7777 <https://github.com/pytest-dev/pytest/issues/7777>`_: Files and directories are now collected in alphabetical order jointly, unless changed by a plugin.
+  Previously, files were collected before directories.
+  See below for an example.
+
+
+- `#8976 <https://github.com/pytest-dev/pytest/issues/8976>`_: Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
+  Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
+  (unless :confval:`python_files` was changed to allow `__init__.py` file).
+
+  To collect the entire package, specify just the directory: `pytest pkg`.
+
+
+- `#11137 <https://github.com/pytest-dev/pytest/issues/11137>`_: :class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File`.
+
+  The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file.
+  Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module),
+  the module being the `__init__.py` file.
+  This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details).
+
+  The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file.
+
+  Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist,
+  if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files).
+
+
+- `#7777 <https://github.com/pytest-dev/pytest/issues/7777>`_: Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass.
+  This is analogous to the existing :class:`pytest.File` for file nodes.
+
+  Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`.
+  A ``Package`` represents a filesystem directory which is a Python package,
+  i.e. contains an ``__init__.py`` file.
+
+  :class:`pytest.Package` now only collects files in its own directory; previously it collected recursively.
+  Sub-directories are collected as their own collector nodes, which then collect themselves, thus creating a collection tree which mirrors the filesystem hierarchy.
+
+  Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`.
+  This node represents a filesystem directory, which is not a :class:`pytest.Package`,
+  that is, does not contain an ``__init__.py`` file.
+  Similarly to ``Package``, it only collects the files in its own directory.
+
+  :class:`pytest.Session` now only collects the initial arguments, without recursing into directories.
+  This work is now done by the :func:`recursive expansion process <pytest.Collector.collect>` of directory collector nodes.
+
+  :attr:`session.name <pytest.Session.name>` is now ``""``; previously it was the rootdir directory name.
+  This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`.
+
+  The collection tree now contains directories/packages up to the :ref:`rootdir <rootdir>`,
+  for initial arguments that are found within the rootdir.
+  For files outside the rootdir, only the immediate directory/package is collected --
+  note however that collecting from outside the rootdir is discouraged.
+
+  As an example, given the following filesystem tree::
+
+      myroot/
+          pytest.ini
+          top/
+          ├── aaa
+          │   └── test_aaa.py
+          ├── test_a.py
+          ├── test_b
+          │   ├── __init__.py
+          │   └── test_b.py
+          ├── test_c.py
+          └── zzz
+              ├── __init__.py
+              └── test_zzz.py
+
+  the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity,
+  is now the following::
+
+      <Session>
+        <Dir myroot>
+          <Dir top>
+            <Dir aaa>
+              <Module test_aaa.py>
+                <Function test_it>
+            <Module test_a.py>
+              <Function test_it>
+            <Package test_b>
+              <Module test_b.py>
+                <Function test_it>
+            <Module test_c.py>
+              <Function test_it>
+            <Package zzz>
+              <Module test_zzz.py>
+                <Function test_it>
+
+  Previously, it was::
+
+      <Session>
+        <Module top/test_a.py>
+          <Function test_it>
+        <Module top/test_c.py>
+          <Function test_it>
+        <Module top/aaa/test_aaa.py>
+          <Function test_it>
+        <Package test_b>
+          <Module test_b.py>
+            <Function test_it>
+        <Package zzz>
+          <Module test_zzz.py>
+            <Function test_it>
+
+  Code/plugins which rely on a specific shape of the collection tree might need to update.
+
+
+- `#11676 <https://github.com/pytest-dev/pytest/issues/11676>`_: The classes :class:`~_pytest.nodes.Node`, :class:`~pytest.Collector`, :class:`~pytest.Item`, :class:`~pytest.File`, :class:`~_pytest.nodes.FSCollector` are now marked abstract (see :mod:`abc`).
+
+  We do not expect this change to affect users and plugin authors, it will only cause errors when the code is already wrong or problematic.
+
+
+Other breaking changes
+^^^^^^^^^^^^^^^^^^^^^^
+
+These are breaking changes where deprecation was not possible.
+
+
+- `#11282 <https://github.com/pytest-dev/pytest/issues/11282>`_: Sanitized the handling of the ``default`` parameter when defining configuration options.
+
+  Previously if ``default`` was not supplied for :meth:`parser.addini <pytest.Parser.addini>` and the configuration option value was not defined in a test session, then calls to :func:`config.getini <pytest.Config.getini>` returned an *empty list* or an *empty string* depending on whether ``type`` was supplied or not respectively, which is clearly incorrect. Also, ``None`` was not honored even if ``default=None`` was used explicitly while defining the option.
+
+  Now the behavior of :meth:`parser.addini <pytest.Parser.addini>` is as follows:
+
+  * If ``default`` is NOT passed but ``type`` is provided, then a type-specific default will be returned. For example ``type=bool`` will return ``False``, ``type=str`` will return ``""``, etc.
+  * If ``default=None`` is passed and the option is not defined in a test session, then ``None`` will be returned, regardless of the ``type``.
+  * If neither ``default`` nor ``type`` are provided, assume ``type=str`` and return ``""`` as default (this is as per previous behavior).
+
+  The team decided to not introduce a deprecation period for this change, as doing so would be complicated both in terms of communicating this to the community as well as implementing it, and also because the team believes this change should not break existing plugins except in rare cases.
+
+
+- `#11667 <https://github.com/pytest-dev/pytest/issues/11667>`_: pytest's ``setup.py`` file is removed.
+  If you relied on this file, e.g. to install pytest using ``setup.py install``,
+  please see `Why you shouldn't invoke setup.py directly <https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html#summary>`_ for alternatives.
+
+
+- `#9288 <https://github.com/pytest-dev/pytest/issues/9288>`_: :func:`~pytest.warns` now re-emits unmatched warnings when the context
+  closes -- previously it would consume all warnings, hiding those that were not
+  matched by the function.
+
+  While this is a new feature, we announce it as a breaking change
+  because many test suites are configured to error-out on warnings, and will
+  therefore fail on the newly-re-emitted warnings.
+
+
+- The internal ``FixtureManager.getfixtureclosure`` method has changed. Plugins which use this method or
+  which subclass ``FixtureManager`` and overwrite that method will need to adapt to the change.
+
+
+
+Deprecations
+------------
+
+- `#10465 <https://github.com/pytest-dev/pytest/issues/10465>`_: Test functions returning a value other than ``None`` will now issue a :class:`pytest.PytestWarning` instead of ``pytest.PytestRemovedIn8Warning``, meaning this will stay a warning instead of becoming an error in the future.
+
+
+- `#3664 <https://github.com/pytest-dev/pytest/issues/3664>`_: Applying a mark to a fixture function now issues a warning: marks in fixtures never had any effect, but it is a common user error to apply a mark to a fixture (for example ``usefixtures``) and expect it to work.
+
+  This will become an error in pytest 9.0.
+
+
+
+Features and Improvements
+-------------------------
+
+Improved Diffs
+^^^^^^^^^^^^^^
+
+These changes improve the diffs that pytest prints when an assertion fails.
+Note that syntax highlighting requires the ``pygments`` package.
+
+
+- `#11520 <https://github.com/pytest-dev/pytest/issues/11520>`_: The very verbose (``-vv``) diff output is now colored as a diff instead of a big chunk of red.
+
+  Python code in error reports is now syntax-highlighted as Python.
+
+  The sections in the error reports are now better separated.
+
+
+- `#1531 <https://github.com/pytest-dev/pytest/issues/1531>`_: The very verbose diff (``-vv``) for every standard library container type is improved. The indentation is now consistent and the markers are on their own separate lines, which should reduce the diffs shown to users.
+
+  Previously, the standard Python pretty printer was used to generate the output, which puts opening and closing
+  markers on the same line as the first/last entry, in addition to not having consistent indentation.
+
+
+- `#10617 <https://github.com/pytest-dev/pytest/issues/10617>`_: Added more comprehensive set assertion rewrites for comparisons other than equality ``==``, with
+  the following operations now providing better failure messages: ``!=``, ``<=``, ``>=``, ``<``, and ``>``.
+
+
+Separate Control For Assertion Verbosity
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- `#11387 <https://github.com/pytest-dev/pytest/issues/11387>`_: Added the new :confval:`verbosity_assertions` configuration option for fine-grained control of failed assertions verbosity.
+
+  If you've ever wished that pytest always show you full diffs, but without making everything else verbose, this is for you.
+
+  See :ref:`Fine-grained verbosity <pytest.fine_grained_verbosity>` for more details.
+
+  For plugin authors, :attr:`config.get_verbosity <pytest.Config.get_verbosity>` can be used to retrieve the verbosity level for a specific verbosity type.
+
+
+Additional Support For Exception Groups and ``__notes__``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+These changes improve pytest's support for exception groups.
+
+
+- `#10441 <https://github.com/pytest-dev/pytest/issues/10441>`_: Added :func:`ExceptionInfo.group_contains() <pytest.ExceptionInfo.group_contains>`, an assertion helper that tests if an :class:`ExceptionGroup` contains a matching exception.
+
+  See :ref:`assert-matching-exception-groups` for an example.
+
+
+- `#11227 <https://github.com/pytest-dev/pytest/issues/11227>`_: Allow :func:`pytest.raises` ``match`` argument to match against `PEP-678 <https://peps.python.org/pep-0678/>` ``__notes__``.
+
+
+Custom Directory collectors
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- `#7777 <https://github.com/pytest-dev/pytest/issues/7777>`_: Added a new hook :hook:`pytest_collect_directory`,
+  which is called by filesystem-traversing collector nodes,
+  such as :class:`pytest.Session`, :class:`pytest.Dir` and :class:`pytest.Package`,
+  to create a collector node for a sub-directory.
+  It is expected to return a subclass of :class:`pytest.Directory`.
+  This hook allows plugins to :ref:`customize the collection of directories <custom directory collectors>`.
+
+
+"New-style" Hook Wrappers
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- `#11122 <https://github.com/pytest-dev/pytest/issues/11122>`_: pytest now uses "new-style" hook wrappers internally, available since pluggy 1.2.0.
+  See `pluggy's 1.2.0 changelog <https://pluggy.readthedocs.io/en/latest/changelog.html#pluggy-1-2-0-2023-06-21>`_ and the :ref:`updated docs <hookwrapper>` for details.
+
+  Plugins which want to use new-style wrappers can do so if they require ``pytest>=8``.
+
+
+Other Improvements
+^^^^^^^^^^^^^^^^^^
+
+- `#11216 <https://github.com/pytest-dev/pytest/issues/11216>`_: If a test is skipped from inside an :ref:`xunit setup fixture <classic xunit>`, the test summary now shows the test location instead of the fixture location.
+
+
+- `#11314 <https://github.com/pytest-dev/pytest/issues/11314>`_: Logging to a file using the ``--log-file`` option will use ``--log-level``, ``--log-format`` and ``--log-date-format`` as fallback
+  if ``--log-file-level``, ``--log-file-format`` and ``--log-file-date-format`` are not provided respectively.
+
+
+- `#11610 <https://github.com/pytest-dev/pytest/issues/11610>`_: Added the :func:`LogCaptureFixture.filtering() <pytest.LogCaptureFixture.filtering>` context manager which
+  adds a given :class:`logging.Filter` object to the :fixture:`caplog` fixture.
+
+
+- `#11447 <https://github.com/pytest-dev/pytest/issues/11447>`_: :func:`pytest.deprecated_call` now also considers warnings of type :class:`FutureWarning`.
+
+
+- `#11600 <https://github.com/pytest-dev/pytest/issues/11600>`_: Improved the documentation and type signature for :func:`pytest.mark.xfail <pytest.mark.xfail>`'s ``condition`` param to use ``False`` as the default value.
+
+
+- `#7469 <https://github.com/pytest-dev/pytest/issues/7469>`_: :class:`~pytest.FixtureDef` is now exported as ``pytest.FixtureDef`` for typing purposes.
+
+
+- `#11353 <https://github.com/pytest-dev/pytest/issues/11353>`_: Added typing to :class:`~pytest.PytestPluginManager`.
+
+
+Bug Fixes
+---------
+
+- `#10701 <https://github.com/pytest-dev/pytest/issues/10701>`_: :meth:`pytest.WarningsRecorder.pop` will return the most-closely-matched warning in the list,
+  rather than the first warning which is an instance of the requested type.
+
+
+- `#11255 <https://github.com/pytest-dev/pytest/issues/11255>`_: Fixed crash on `parametrize(..., scope="package")` without a package present.
+
+
+- `#11277 <https://github.com/pytest-dev/pytest/issues/11277>`_: Fixed a bug that when there are multiple fixtures for an indirect parameter,
+  the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope.
+
+
+- `#11456 <https://github.com/pytest-dev/pytest/issues/11456>`_: Parametrized tests now *really do* ensure that the ids given to each input are unique - for
+  example, ``a, a, a0`` now results in ``a1, a2, a0`` instead of the previous (buggy) ``a0, a1, a0``.
+  This necessarily means changing nodeids where these were previously colliding, and for
+  readability adds an underscore when non-unique ids end in a number.
+
+
+- `#11563 <https://github.com/pytest-dev/pytest/issues/11563>`_: Fixed a crash when using an empty string for the same parametrized value more than once.
+
+
+- `#11712 <https://github.com/pytest-dev/pytest/issues/11712>`_: Fixed handling ``NO_COLOR`` and ``FORCE_COLOR`` to ignore an empty value.
+
+
+- `#9036 <https://github.com/pytest-dev/pytest/issues/9036>`_: ``pytest.warns`` and similar functions now capture warnings when an exception is raised inside a ``with`` block.
+
+
+
+Improved Documentation
+----------------------
+
+- `#11011 <https://github.com/pytest-dev/pytest/issues/11011>`_: Added a warning about modifying the root logger during tests when using ``caplog``.
+
+
+- `#11065 <https://github.com/pytest-dev/pytest/issues/11065>`_: Use ``pytestconfig`` instead of ``request.config`` in cache example to be consistent with the API documentation.
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#11208 <https://github.com/pytest-dev/pytest/issues/11208>`_: The (internal) ``FixtureDef.cached_result`` type has changed.
+  Now the third item ``cached_result[2]``, when set, is an exception instance instead of an exception triplet.
+
+
+- `#11218 <https://github.com/pytest-dev/pytest/issues/11218>`_: (This entry is meant to assist plugins which access private pytest internals to instantiate ``FixtureRequest`` objects.)
+
+  :class:`~pytest.FixtureRequest` is now an abstract class which can't be instantiated directly.
+  A new concrete ``TopRequest`` subclass of ``FixtureRequest`` has been added for the ``request`` fixture in test functions,
+  as counterpart to the existing ``SubRequest`` subclass for the ``request`` fixture in fixture functions.
+
+
+- `#11315 <https://github.com/pytest-dev/pytest/issues/11315>`_: The :fixture:`pytester` fixture now uses the :fixture:`monkeypatch` fixture to manage the current working directory.
+  If you use ``pytester`` in combination with :func:`monkeypatch.undo() <pytest.MonkeyPatch.undo>`, the CWD might get restored.
+  Use :func:`monkeypatch.context() <pytest.MonkeyPatch.context>` instead.
+
+
+- `#11333 <https://github.com/pytest-dev/pytest/issues/11333>`_: Corrected the spelling of ``Config.ArgsSource.INVOCATION_DIR``.
+  The previous spelling ``INCOVATION_DIR`` remains as an alias.
+
+
+- `#11638 <https://github.com/pytest-dev/pytest/issues/11638>`_: Fixed the selftests to pass correctly if ``FORCE_COLOR``, ``NO_COLOR`` or ``PY_COLORS`` is set in the calling environment.
+
+pytest 7.4.4 (2023-12-31)
+=========================
+
+Bug Fixes
+---------
+
+- `#11140 <https://github.com/pytest-dev/pytest/issues/11140>`_: Fix non-string constants at the top of file being detected as docstrings on Python>=3.8.
+
+
+- `#11572 <https://github.com/pytest-dev/pytest/issues/11572>`_: Handle an edge case where :data:`sys.stderr` and :data:`sys.__stderr__` might already be closed when :ref:`faulthandler` is tearing down.
+
+
+- `#11710 <https://github.com/pytest-dev/pytest/issues/11710>`_: Fixed tracebacks from collection errors not getting pruned.
+
+
+- `#7966 <https://github.com/pytest-dev/pytest/issues/7966>`_: Removed unhelpful error message from assertion rewrite mechanism when exceptions are raised in ``__iter__`` methods. Now they are treated un-iterable instead.
+
+
+
+Improved Documentation
+----------------------
+
+- `#11091 <https://github.com/pytest-dev/pytest/issues/11091>`_: Updated documentation to refer to hyphenated options: replaced ``--junitxml`` with ``--junit-xml`` and ``--collectonly`` with ``--collect-only``.
+
+
+pytest 7.4.3 (2023-10-24)
+=========================
+
+Bug Fixes
+---------
+
+- `#10447 <https://github.com/pytest-dev/pytest/issues/10447>`_: Markers are now considered in the reverse mro order to ensure base  class markers are considered first -- this resolves a regression.
+
+
+- `#11239 <https://github.com/pytest-dev/pytest/issues/11239>`_: Fixed ``:=`` in asserts impacting unrelated test cases.
+
+
+- `#11439 <https://github.com/pytest-dev/pytest/issues/11439>`_: Handled an edge case where :data:`sys.stderr` might already be closed when :ref:`faulthandler` is tearing down.
+
+
+pytest 7.4.2 (2023-09-07)
+=========================
+
+Bug Fixes
+---------
+
+- `#11237 <https://github.com/pytest-dev/pytest/issues/11237>`_: Fix doctest collection of `functools.cached_property` objects.
+
+
+- `#11306 <https://github.com/pytest-dev/pytest/issues/11306>`_: Fixed bug using ``--importmode=importlib`` which would cause package ``__init__.py`` files to be imported more than once in some cases.
+
+
+- `#11367 <https://github.com/pytest-dev/pytest/issues/11367>`_: Fixed bug where `user_properties` where not being saved in the JUnit XML file if a fixture failed during teardown.
+
+
+- `#11394 <https://github.com/pytest-dev/pytest/issues/11394>`_: Fixed crash when parsing long command line arguments that might be interpreted as files.
+
+
+
+Improved Documentation
+----------------------
+
+- `#11391 <https://github.com/pytest-dev/pytest/issues/11391>`_: Improved disclaimer on pytest plugin reference page to better indicate this is an automated, non-curated listing.
+
+
+pytest 7.4.1 (2023-09-02)
+=========================
+
+Bug Fixes
+---------
+
+- `#10337 <https://github.com/pytest-dev/pytest/issues/10337>`_: Fixed bug where fake intermediate modules generated by ``--import-mode=importlib`` would not include the
+  child modules as attributes of the parent modules.
+
+
+- `#10702 <https://github.com/pytest-dev/pytest/issues/10702>`_: Fixed error assertion handling in :func:`pytest.approx` when ``None`` is an expected or received value when comparing dictionaries.
+
+
+- `#10811 <https://github.com/pytest-dev/pytest/issues/10811>`_: Fixed issue when using ``--import-mode=importlib`` together with ``--doctest-modules`` that caused modules
+  to be imported more than once, causing problems with modules that have import side effects.
+
+
+pytest 7.4.0 (2023-06-23)
+=========================
+
+Features
+--------
+
+- `#10901 <https://github.com/pytest-dev/pytest/issues/10901>`_: Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
+  This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.
+
+
+
+Improvements
+------------
+
+- `#10872 <https://github.com/pytest-dev/pytest/issues/10872>`_: Update test log report annotation to named tuple and fixed inconsistency in docs for :hook:`pytest_report_teststatus` hook.
+
+
+- `#10907 <https://github.com/pytest-dev/pytest/issues/10907>`_: When an exception traceback to be displayed is completely filtered out (by mechanisms such as ``__tracebackhide__``, internal frames, and similar), now only the exception string and the following message are shown:
+
+  "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames.".
+
+  Previously, the last frame of the traceback was shown, even though it was hidden.
+
+
+- `#10940 <https://github.com/pytest-dev/pytest/issues/10940>`_: Improved verbose output (``-vv``) of ``skip`` and ``xfail`` reasons by performing text wrapping while leaving a clear margin for progress output.
+
+  Added ``TerminalReporter.wrap_write()`` as a helper for that.
+
+
+- `#10991 <https://github.com/pytest-dev/pytest/issues/10991>`_: Added handling of ``%f`` directive to print microseconds in log format options, such as ``log-date-format``.
+
+
+- `#11005 <https://github.com/pytest-dev/pytest/issues/11005>`_: Added the underlying exception to the cache provider's path creation and write warning messages.
+
+
+- `#11013 <https://github.com/pytest-dev/pytest/issues/11013>`_: Added warning when :confval:`testpaths` is set, but paths are not found by glob. In this case, pytest will fall back to searching from the current directory.
+
+
+- `#11043 <https://github.com/pytest-dev/pytest/issues/11043>`_: When `--confcutdir` is not specified, and there is no config file present, the conftest cutoff directory (`--confcutdir`) is now set to the :ref:`rootdir <rootdir>`.
+  Previously in such cases, `conftest.py` files would be probed all the way to the root directory of the filesystem.
+  If you are badly affected by this change, consider adding an empty config file to your desired cutoff directory, or explicitly set `--confcutdir`.
+
+
+- `#11081 <https://github.com/pytest-dev/pytest/issues/11081>`_: The :confval:`norecursedirs` check is now performed in a :hook:`pytest_ignore_collect` implementation, so plugins can affect it.
+
+  If after updating to this version you see that your `norecursedirs` setting is not being respected,
+  it means that a conftest or a plugin you use has a bad `pytest_ignore_collect` implementation.
+  Most likely, your hook returns `False` for paths it does not want to ignore,
+  which ends the processing and doesn't allow other plugins, including pytest itself, to ignore the path.
+  The fix is to return `None` instead of `False` for paths your hook doesn't want to ignore.
+
+
+- `#8711 <https://github.com/pytest-dev/pytest/issues/8711>`_: :func:`caplog.set_level() <pytest.LogCaptureFixture.set_level>` and :func:`caplog.at_level() <pytest.LogCaptureFixture.at_level>`
+  will temporarily enable the requested ``level`` if ``level`` was disabled globally via
+  ``logging.disable(LEVEL)``.
+
+
+
+Bug Fixes
+---------
+
+- `#10831 <https://github.com/pytest-dev/pytest/issues/10831>`_: Terminal Reporting: Fixed bug when running in ``--tb=line`` mode where ``pytest.fail(pytrace=False)`` tests report ``None``.
+
+
+- `#11068 <https://github.com/pytest-dev/pytest/issues/11068>`_: Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files <non-python tests>`.
+
+
+- `#11104 <https://github.com/pytest-dev/pytest/issues/11104>`_: Fixed a regression in pytest 7.3.2 which caused to :confval:`testpaths` to be considered for loading initial conftests,
+  even when it was not utilized (e.g. when explicit paths were given on the command line).
+  Now the ``testpaths`` are only considered when they are in use.
+
+
+- `#1904 <https://github.com/pytest-dev/pytest/issues/1904>`_: Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message).
+
+
+- `#7781 <https://github.com/pytest-dev/pytest/issues/7781>`_: Fix writing non-encodable text to log file when using ``--debug``.
+
+
+
+Improved Documentation
+----------------------
+
+- `#9146 <https://github.com/pytest-dev/pytest/issues/9146>`_: Improved documentation for :func:`caplog.set_level() <pytest.LogCaptureFixture.set_level>`.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#11031 <https://github.com/pytest-dev/pytest/issues/11031>`_: Enhanced the CLI flag for ``-c`` to now include ``--config-file`` to make it clear that this flag applies to the usage of a custom config file.
+
+
+pytest 7.3.2 (2023-06-10)
+=========================
+
+Bug Fixes
+---------
+
+- `#10169 <https://github.com/pytest-dev/pytest/issues/10169>`_: Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems.
+
+
+- `#10894 <https://github.com/pytest-dev/pytest/issues/10894>`_: Support for Python 3.12 (beta at the time of writing).
+
+
+- `#10987 <https://github.com/pytest-dev/pytest/issues/10987>`_: :confval:`testpaths` is now honored to load root ``conftests``.
+
+
+- `#10999 <https://github.com/pytest-dev/pytest/issues/10999>`_: The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments.
+
+
+- `#11028 <https://github.com/pytest-dev/pytest/issues/11028>`_: Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call.
+
+
+- `#11054 <https://github.com/pytest-dev/pytest/issues/11054>`_: Fixed ``--last-failed``'s "(skipped N files)" functionality for files inside of packages (directories with `__init__.py` files).
+
+
+pytest 7.3.1 (2023-04-14)
+=========================
+
+Improvements
+------------
+
+- `#10875 <https://github.com/pytest-dev/pytest/issues/10875>`_: Python 3.12 support: fixed ``RuntimeError: TestResult has no addDuration method`` when running ``unittest`` tests.
+
+
+- `#10890 <https://github.com/pytest-dev/pytest/issues/10890>`_: Python 3.12 support: fixed ``shutil.rmtree(onerror=...)`` deprecation warning when using :fixture:`tmp_path`.
+
+
+
+Bug Fixes
+---------
+
+- `#10896 <https://github.com/pytest-dev/pytest/issues/10896>`_: Fixed performance regression related to :fixture:`tmp_path` and the new :confval:`tmp_path_retention_policy` option.
+
+
+- `#10903 <https://github.com/pytest-dev/pytest/issues/10903>`_: Fix crash ``INTERNALERROR IndexError: list index out of range`` which happens when displaying an exception where all entries are hidden.
+  This reverts the change "Correctly handle ``__tracebackhide__`` for chained exceptions." introduced in version 7.3.0.
+
+
+pytest 7.3.0 (2023-04-08)
+=========================
+
+Features
+--------
+
+- `#10525 <https://github.com/pytest-dev/pytest/issues/10525>`_: Test methods decorated with ``@classmethod`` can now be discovered as tests, following the same rules as normal methods. This fills the gap that static methods were discoverable as tests but not class methods.
+
+
+- `#10755 <https://github.com/pytest-dev/pytest/issues/10755>`_: :confval:`console_output_style` now supports ``progress-even-when-capture-no`` to force the use of the progress output even when capture is disabled. This is useful in large test suites where capture may have significant performance impact.
+
+
+- `#7431 <https://github.com/pytest-dev/pytest/issues/7431>`_: ``--log-disable`` CLI option added to disable individual loggers.
+
+
+- `#8141 <https://github.com/pytest-dev/pytest/issues/8141>`_: Added :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy` configuration options to control how directories created by the :fixture:`tmp_path` fixture are kept.
+
+
+
+Improvements
+------------
+
+- `#10226 <https://github.com/pytest-dev/pytest/issues/10226>`_: If multiple errors are raised in teardown, we now re-raise an ``ExceptionGroup`` of them instead of discarding all but the last.
+
+
+- `#10658 <https://github.com/pytest-dev/pytest/issues/10658>`_: Allow ``-p`` arguments to include spaces (eg: ``-p no:logging`` instead of
+  ``-pno:logging``). Mostly useful in the ``addopts`` section of the configuration
+  file.
+
+
+- `#10710 <https://github.com/pytest-dev/pytest/issues/10710>`_: Added ``start`` and ``stop`` timestamps to ``TestReport`` objects.
+
+
+- `#10727 <https://github.com/pytest-dev/pytest/issues/10727>`_: Split the report header for ``rootdir``, ``config file`` and ``testpaths`` so each has its own line.
+
+
+- `#10840 <https://github.com/pytest-dev/pytest/issues/10840>`_: pytest should no longer crash on AST with pathological position attributes, for example testing AST produced by `Hylang <https://github.com/hylang/hy>__`.
+
+
+- `#6267 <https://github.com/pytest-dev/pytest/issues/6267>`_: The full output of a test is no longer truncated if the truncation message would be longer than
+  the hidden text. The line number shown has also been fixed.
+
+
+
+Bug Fixes
+---------
+
+- `#10743 <https://github.com/pytest-dev/pytest/issues/10743>`_: The assertion rewriting mechanism now works correctly when assertion expressions contain the walrus operator.
+
+
+- `#10765 <https://github.com/pytest-dev/pytest/issues/10765>`_: Fixed :fixture:`tmp_path` fixture always raising :class:`OSError` on ``emscripten`` platform due to missing :func:`os.getuid`.
+
+
+- `#1904 <https://github.com/pytest-dev/pytest/issues/1904>`_: Correctly handle ``__tracebackhide__`` for chained exceptions.
+  NOTE: This change was reverted in version 7.3.1.
+
+
+
+Improved Documentation
+----------------------
+
+- `#10782 <https://github.com/pytest-dev/pytest/issues/10782>`_: Fixed the minimal example in :ref:`goodpractices`: ``pip install -e .`` requires a ``version`` entry in ``pyproject.toml`` to run successfully.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#10669 <https://github.com/pytest-dev/pytest/issues/10669>`_: pytest no longer directly depends on the `attrs <https://www.attrs.org/en/stable/>`__ package. While
+  we at pytest all love the package dearly and would like to thank the ``attrs`` team for many years of cooperation and support,
+  it makes sense for ``pytest`` to have as little external dependencies as possible, as this helps downstream projects.
+  With that in mind, we have replaced the pytest's limited internal usage to use the standard library's ``dataclasses`` instead.
+
+  Nice diffs for ``attrs`` classes are still supported though.
+
+
+pytest 7.2.2 (2023-03-03)
+=========================
+
+Bug Fixes
+---------
+
+- `#10533 <https://github.com/pytest-dev/pytest/issues/10533>`_: Fixed :func:`pytest.approx` handling of dictionaries containing one or more values of `0.0`.
+
+
+- `#10592 <https://github.com/pytest-dev/pytest/issues/10592>`_: Fixed crash if `--cache-show` and `--help` are passed at the same time.
+
+
+- `#10597 <https://github.com/pytest-dev/pytest/issues/10597>`_: Fixed bug where a fixture method named ``teardown`` would be called as part of ``nose`` teardown stage.
+
+
+- `#10626 <https://github.com/pytest-dev/pytest/issues/10626>`_: Fixed crash if ``--fixtures`` and ``--help`` are passed at the same time.
+
+
+- `#10660 <https://github.com/pytest-dev/pytest/issues/10660>`_: Fixed :py:func:`pytest.raises` to return a 'ContextManager' so that type-checkers could narrow
+  :code:`pytest.raises(...) if ... else nullcontext()` down to 'ContextManager' rather than 'object'.
+
+
+
+Improved Documentation
+----------------------
+
+- `#10690 <https://github.com/pytest-dev/pytest/issues/10690>`_: Added `CI` and `BUILD_NUMBER` environment variables to the documentation.
+
+
+- `#10721 <https://github.com/pytest-dev/pytest/issues/10721>`_: Fixed entry-points declaration in the documentation example using Hatch.
+
+
+- `#10753 <https://github.com/pytest-dev/pytest/issues/10753>`_: Changed wording of the module level skip to be very explicit
+  about not collecting tests and not executing the rest of the module.
+
+
+pytest 7.2.1 (2023-01-13)
+=========================
+
+Bug Fixes
+---------
+
+- `#10452 <https://github.com/pytest-dev/pytest/issues/10452>`_: Fix 'importlib.abc.TraversableResources' deprecation warning in Python 3.12.
+
+
+- `#10457 <https://github.com/pytest-dev/pytest/issues/10457>`_: If a test is skipped from inside a fixture, the test summary now shows the test location instead of the fixture location.
+
+
+- `#10506 <https://github.com/pytest-dev/pytest/issues/10506>`_: Fix bug where sometimes pytest would use the file system root directory as :ref:`rootdir <rootdir>` on Windows.
+
+
+- `#10607 <https://github.com/pytest-dev/pytest/issues/10607>`_: Fix a race condition when creating junitxml reports, which could occur when multiple instances of pytest execute in parallel.
+
+
+- `#10641 <https://github.com/pytest-dev/pytest/issues/10641>`_: Fix a race condition when creating or updating the stepwise plugin's cache, which could occur when multiple xdist worker nodes try to simultaneously update the stepwise plugin's cache.
+
+
+pytest 7.2.0 (2022-10-23)
+=========================
+
+Deprecations
+------------
+
+- `#10012 <https://github.com/pytest-dev/pytest/issues/10012>`_: Update ``pytest.PytestUnhandledCoroutineWarning`` to a deprecation; it will raise an error in pytest 8.
+
+
+- `#10396 <https://github.com/pytest-dev/pytest/issues/10396>`_: pytest no longer depends on the ``py`` library.  ``pytest`` provides a vendored copy of ``py.error`` and ``py.path`` modules but will use the ``py`` library if it is installed.  If you need other ``py.*`` modules, continue to install the deprecated ``py`` library separately, otherwise it can usually be removed as a dependency.
+
+
+- `#4562 <https://github.com/pytest-dev/pytest/issues/4562>`_: Deprecate configuring hook specs/impls using attributes/marks.
+
+  Instead use :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec`.
+  For more details, see the :ref:`docs <legacy-path-hooks-deprecated>`.
+
+
+- `#9886 <https://github.com/pytest-dev/pytest/issues/9886>`_: The functionality for running tests written for ``nose`` has been officially deprecated.
+
+  This includes:
+
+  * Plain ``setup`` and ``teardown`` functions and methods: this might catch users by surprise, as ``setup()`` and ``teardown()`` are not pytest idioms, but part of the ``nose`` support.
+  * Setup/teardown using the `@with_setup <with-setup-nose>`_ decorator.
+
+  For more details, consult the :ref:`deprecation docs <nose-deprecation>`.
+
+  .. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup
+
+- `#7337 <https://github.com/pytest-dev/pytest/issues/7337>`_: A deprecation warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`. The plan is to make returning non-`None` from tests an error in the future.
+
+
+Features
+--------
+
+- `#9897 <https://github.com/pytest-dev/pytest/issues/9897>`_: Added shell-style wildcard support to ``testpaths``.
+
+
+
+Improvements
+------------
+
+- `#10218 <https://github.com/pytest-dev/pytest/issues/10218>`_: ``@pytest.mark.parametrize()`` (and similar functions) now accepts any ``Sequence[str]`` for the argument names,
+  instead of just ``list[str]`` and ``tuple[str, ...]``.
+
+  (Note that ``str``, which is itself a ``Sequence[str]``, is still treated as a
+  comma-delimited name list, as before).
+
+
+- `#10381 <https://github.com/pytest-dev/pytest/issues/10381>`_: The ``--no-showlocals`` flag has been added. This can be passed directly to tests to override ``--showlocals`` declared through ``addopts``.
+
+
+- `#3426 <https://github.com/pytest-dev/pytest/issues/3426>`_: Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expressed instead.
+
+
+- `#8508 <https://github.com/pytest-dev/pytest/issues/8508>`_: Introduce multiline display for warning matching  via :py:func:`pytest.warns` and
+  enhance match comparison for :py:func:`pytest.ExceptionInfo.match` as returned by :py:func:`pytest.raises`.
+
+
+- `#8646 <https://github.com/pytest-dev/pytest/issues/8646>`_: Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing
+  error. We now raise immediately with a more helpful message.
+
+
+- `#9741 <https://github.com/pytest-dev/pytest/issues/9741>`_: On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML.
+
+  `tomli` is no longer a dependency on Python 3.11.
+
+
+- `#9742 <https://github.com/pytest-dev/pytest/issues/9742>`_: Display assertion message without escaped newline characters with ``-vv``.
+
+
+- `#9823 <https://github.com/pytest-dev/pytest/issues/9823>`_: Improved error message that is shown when no collector is found for a given file.
+
+
+- `#9873 <https://github.com/pytest-dev/pytest/issues/9873>`_: Some coloring has been added to the short test summary.
+
+
+- `#9883 <https://github.com/pytest-dev/pytest/issues/9883>`_: Normalize the help description of all command-line options.
+
+
+- `#9920 <https://github.com/pytest-dev/pytest/issues/9920>`_: Display full crash messages in ``short test summary info``, when running in a CI environment.
+
+
+- `#9987 <https://github.com/pytest-dev/pytest/issues/9987>`_: Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``.
+
+
+
+Bug Fixes
+---------
+
+- `#10150 <https://github.com/pytest-dev/pytest/issues/10150>`_: :data:`sys.stdin` now contains all expected methods of a file-like object when capture is enabled.
+
+
+- `#10382 <https://github.com/pytest-dev/pytest/issues/10382>`_: Do not break into pdb when ``raise unittest.SkipTest()`` appears top-level in a file.
+
+
+- `#7792 <https://github.com/pytest-dev/pytest/issues/7792>`_: Marks are now inherited according to the full MRO in test classes. Previously, if a test class inherited from two or more classes, only marks from the first super-class would apply.
+
+  When inheriting marks from super-classes, marks from the sub-classes are now ordered before marks from the super-classes, in MRO order. Previously it was the reverse.
+
+  When inheriting marks from super-classes, the `pytestmark` attribute of the sub-class now only contains the marks directly applied to it. Previously, it also contained marks from its super-classes. Please note that this attribute should not normally be accessed directly; use :func:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` instead.
+
+
+- `#9159 <https://github.com/pytest-dev/pytest/issues/9159>`_: Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``.
+
+
+- `#9877 <https://github.com/pytest-dev/pytest/issues/9877>`_: Ensure ``caplog.get_records(when)`` returns current/correct data after invoking ``caplog.clear()``.
+
+
+
+Improved Documentation
+----------------------
+
+- `#10344 <https://github.com/pytest-dev/pytest/issues/10344>`_: Update information on writing plugins to use ``pyproject.toml`` instead of ``setup.py``.
+
+
+- `#9248 <https://github.com/pytest-dev/pytest/issues/9248>`_: The documentation is now built using Sphinx 5.x (up from 3.x previously).
+
+
+- `#9291 <https://github.com/pytest-dev/pytest/issues/9291>`_: Update documentation on how :func:`pytest.warns` affects :class:`DeprecationWarning`.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#10313 <https://github.com/pytest-dev/pytest/issues/10313>`_: Made ``_pytest.doctest.DoctestItem`` export ``pytest.DoctestItem`` for
+  type check and runtime purposes. Made `_pytest.doctest` use internal APIs
+  to avoid circular imports.
+
+
+- `#9906 <https://github.com/pytest-dev/pytest/issues/9906>`_: Made ``_pytest.compat`` re-export ``importlib_metadata`` in the eyes of type checkers.
+
+
+- `#9910 <https://github.com/pytest-dev/pytest/issues/9910>`_: Fix default encoding warning (``EncodingWarning``) in ``cacheprovider``
+
+
+- `#9984 <https://github.com/pytest-dev/pytest/issues/9984>`_: Improve the error message when we attempt to access a fixture that has been
+  torn down.
+  Add an additional sentence to the docstring explaining when it's not a good
+  idea to call ``getfixturevalue``.
+
+
+pytest 7.1.3 (2022-08-31)
+=========================
+
+Bug Fixes
+---------
+
+- `#10060 <https://github.com/pytest-dev/pytest/issues/10060>`_: When running with ``--pdb``, ``TestCase.tearDown`` is no longer called for tests when the *class* has been skipped via ``unittest.skip`` or ``pytest.mark.skip``.
+
+
+- `#10190 <https://github.com/pytest-dev/pytest/issues/10190>`_: Invalid XML characters in setup or teardown error messages are now properly escaped for JUnit XML reports.
+
+
+- `#10230 <https://github.com/pytest-dev/pytest/issues/10230>`_: Ignore ``.py`` files created by ``pyproject.toml``-based editable builds introduced in `pip 21.3 <https://pip.pypa.io/en/stable/news/#v21-3>`__.
+
+
+- `#3396 <https://github.com/pytest-dev/pytest/issues/3396>`_: Doctests now respect the ``--import-mode`` flag.
+
+
+- `#9514 <https://github.com/pytest-dev/pytest/issues/9514>`_: Type-annotate ``FixtureRequest.param`` as ``Any`` as a stop gap measure until :issue:`8073` is fixed.
+
+
+- `#9791 <https://github.com/pytest-dev/pytest/issues/9791>`_: Fixed a path handling code in ``rewrite.py`` that seems to work fine, but was incorrect and fails in some systems.
+
+
+- `#9917 <https://github.com/pytest-dev/pytest/issues/9917>`_: Fixed string representation for :func:`pytest.approx` when used to compare tuples.
+
+
+
+Improved Documentation
+----------------------
+
+- `#9937 <https://github.com/pytest-dev/pytest/issues/9937>`_: Explicit note that :fixture:`tmpdir` fixture is discouraged in favour of :fixture:`tmp_path`.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#10114 <https://github.com/pytest-dev/pytest/issues/10114>`_: Replace `atomicwrites <https://github.com/untitaker/python-atomicwrites>`__ dependency on windows with `os.replace`.
+
+
+pytest 7.1.2 (2022-04-23)
+=========================
+
+Bug Fixes
+---------
+
+- `#9726 <https://github.com/pytest-dev/pytest/issues/9726>`_: An unnecessary ``numpy`` import inside :func:`pytest.approx` was removed.
+
+
+- `#9820 <https://github.com/pytest-dev/pytest/issues/9820>`_: Fix comparison of  ``dataclasses`` with ``InitVar``.
+
+
+- `#9869 <https://github.com/pytest-dev/pytest/issues/9869>`_: Increase ``stacklevel`` for the ``NODE_CTOR_FSPATH_ARG`` deprecation to point to the
+  user's code, not pytest.
+
+
+- `#9871 <https://github.com/pytest-dev/pytest/issues/9871>`_: Fix a bizarre (and fortunately rare) bug where the `temp_path` fixture could raise
+  an internal error while attempting to get the current user's username.
+
+
+pytest 7.1.1 (2022-03-17)
+=========================
+
+Bug Fixes
+---------
+
+- `#9767 <https://github.com/pytest-dev/pytest/issues/9767>`_: Fixed a regression in pytest 7.1.0 where some conftest.py files outside of the source tree (e.g. in the `site-packages` directory) were not picked up.
+
+
+pytest 7.1.0 (2022-03-13)
+=========================
+
+Breaking Changes
+----------------
+
+- `#8838 <https://github.com/pytest-dev/pytest/issues/8838>`_: As per our policy, the following features have been deprecated in the 6.X series and are now
+  removed:
+
+  * ``pytest._fillfuncargs`` function.
+
+  * ``pytest_warning_captured`` hook - use ``pytest_warning_recorded`` instead.
+
+  * ``-k -foobar`` syntax - use ``-k 'not foobar'`` instead.
+
+  * ``-k foobar:`` syntax.
+
+  * ``pytest.collect`` module - import from ``pytest`` directly.
+
+  For more information consult
+  `Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ in the docs.
+
+
+- `#9437 <https://github.com/pytest-dev/pytest/issues/9437>`_: Dropped support for Python 3.6, which reached `end-of-life <https://devguide.python.org/#status-of-python-branches>`__ at 2021-12-23.
+
+
+
+Improvements
+------------
+
+- `#5192 <https://github.com/pytest-dev/pytest/issues/5192>`_: Fixed test output for some data types where ``-v`` would show less information.
+
+  Also, when showing diffs for sequences, ``-q`` would produce full diffs instead of the expected diff.
+
+
+- `#9362 <https://github.com/pytest-dev/pytest/issues/9362>`_: pytest now avoids specialized assert formatting when it is detected that the default ``__eq__`` is overridden in ``attrs`` or ``dataclasses``.
+
+
+- `#9536 <https://github.com/pytest-dev/pytest/issues/9536>`_: When ``-vv`` is given on command line, show skipping and xfail reasons in full instead of truncating them to fit the terminal width.
+
+
+- `#9644 <https://github.com/pytest-dev/pytest/issues/9644>`_: More information about the location of resources that led Python to raise :class:`ResourceWarning` can now
+  be obtained by enabling :mod:`tracemalloc`.
+
+  See :ref:`resource-warnings` for more information.
+
+
+- `#9678 <https://github.com/pytest-dev/pytest/issues/9678>`_: More types are now accepted in the ``ids`` argument to ``@pytest.mark.parametrize``.
+  Previously only `str`, `float`, `int` and `bool` were accepted;
+  now `bytes`, `complex`, `re.Pattern`, `Enum` and anything with a `__name__` are also accepted.
+
+
+- `#9692 <https://github.com/pytest-dev/pytest/issues/9692>`_: :func:`pytest.approx` now raises a :class:`TypeError` when given an unordered sequence (such as :class:`set`).
+
+  Note that this implies that custom classes which only implement ``__iter__`` and ``__len__`` are no longer supported as they don't guarantee order.
+
+
+
+Bug Fixes
+---------
+
+- `#8242 <https://github.com/pytest-dev/pytest/issues/8242>`_: The deprecation of raising :class:`unittest.SkipTest` to skip collection of
+  tests during the pytest collection phase is reverted - this is now a supported
+  feature again.
+
+
+- `#9493 <https://github.com/pytest-dev/pytest/issues/9493>`_: Symbolic link components are no longer resolved in conftest paths.
+  This means that if a conftest appears twice in collection tree, using symlinks, it will be executed twice.
+  For example, given
+
+      tests/real/conftest.py
+      tests/real/test_it.py
+      tests/link -> tests/real
+
+  running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``.
+  This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pr:`6523` for details).
+
+
+- `#9626 <https://github.com/pytest-dev/pytest/issues/9626>`_: Fixed count of selected tests on terminal collection summary when there were errors or skipped modules.
+
+  If there were errors or skipped modules on collection, pytest would mistakenly subtract those from the selected count.
+
+
+- `#9645 <https://github.com/pytest-dev/pytest/issues/9645>`_: Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites.
+
+
+- `#9708 <https://github.com/pytest-dev/pytest/issues/9708>`_: :fixture:`pytester` now requests a :fixture:`monkeypatch` fixture instead of creating one internally. This solves some issues with tests that involve pytest environment variables.
+
+
+- `#9730 <https://github.com/pytest-dev/pytest/issues/9730>`_: Malformed ``pyproject.toml`` files now produce a clearer error message.
+
+
+pytest 7.0.1 (2022-02-11)
+=========================
+
+Bug Fixes
+---------
+
+- `#9608 <https://github.com/pytest-dev/pytest/issues/9608>`_: Fix invalid importing of ``importlib.readers`` in Python 3.9.
+
+
+- `#9610 <https://github.com/pytest-dev/pytest/issues/9610>`_: Restore `UnitTestFunction.obj` to return unbound rather than bound method.
+  Fixes a crash during a failed teardown in unittest TestCases with non-default `__init__`.
+  Regressed in pytest 7.0.0.
+
+
+- `#9636 <https://github.com/pytest-dev/pytest/issues/9636>`_: The ``pythonpath`` plugin was renamed to ``python_path``. This avoids a conflict with the ``pytest-pythonpath`` plugin.
+
+
+- `#9642 <https://github.com/pytest-dev/pytest/issues/9642>`_: Fix running tests by id with ``::`` in the parametrize portion.
+
+
+- `#9643 <https://github.com/pytest-dev/pytest/issues/9643>`_: Delay issuing a :class:`~pytest.PytestWarning` about diamond inheritance involving :class:`~pytest.Item` and
+  :class:`~pytest.Collector` so it can be filtered using :ref:`standard warning filters <warnings>`.
+
+
+pytest 7.0.0 (2022-02-03)
+=========================
+
+(**Please see the full set of changes for this release also in the 7.0.0rc1 notes below**)
+
+Deprecations
+------------
+
+- `#9488 <https://github.com/pytest-dev/pytest/issues/9488>`_: If custom subclasses of nodes like :class:`pytest.Item` override the
+  ``__init__`` method, they should take ``**kwargs``. See
+  :ref:`uncooperative-constructors-deprecated` for details.
+
+  Note that a deprecation warning is only emitted when there is a conflict in the
+  arguments pytest expected to pass. This deprecation was already part of pytest
+  7.0.0rc1 but wasn't documented.
+
+
+
+Bug Fixes
+---------
+
+- `#9355 <https://github.com/pytest-dev/pytest/issues/9355>`_: Fixed error message prints function decorators when using assert in Python 3.8 and above.
+
+
+- `#9396 <https://github.com/pytest-dev/pytest/issues/9396>`_: Ensure `pytest.Config.inifile` is available during the :hook:`pytest_cmdline_main` hook (regression during ``7.0.0rc1``).
+
+
+
+Improved Documentation
+----------------------
+
+- `#9404 <https://github.com/pytest-dev/pytest/issues/9404>`_: Added extra documentation on alternatives to common misuses of `pytest.warns(None)` ahead of its deprecation.
+
+
+- `#9505 <https://github.com/pytest-dev/pytest/issues/9505>`_: Clarify where the configuration files are located. To avoid confusions documentation mentions
+  that configuration file is located in the root of the repository.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#9521 <https://github.com/pytest-dev/pytest/issues/9521>`_: Add test coverage to assertion rewrite path.
+
+
+pytest 7.0.0rc1 (2021-12-06)
+============================
+
+Breaking Changes
+----------------
+
+- `#7259 <https://github.com/pytest-dev/pytest/issues/7259>`_: The :ref:`Node.reportinfo() <non-python tests>` function first return value type has been expanded from `py.path.local | str` to `os.PathLike[str] | str`.
+
+  Most plugins which refer to `reportinfo()` only define it as part of a custom :class:`pytest.Item` implementation.
+  Since `py.path.local` is an `os.PathLike[str]`, these plugins are unaffected.
+
+  Plugins and users which call `reportinfo()`, use the first return value and interact with it as a `py.path.local`, would need to adjust by calling `py.path.local(fspath)`.
+  Although preferably, avoid the legacy `py.path.local` and use `pathlib.Path`, or use `item.location` or `item.path`, instead.
+
+  Note: pytest was not able to provide a deprecation period for this change.
+
+
+- `#8246 <https://github.com/pytest-dev/pytest/issues/8246>`_: ``--version`` now writes version information to ``stdout`` rather than ``stderr``.
+
+
+- `#8733 <https://github.com/pytest-dev/pytest/issues/8733>`_: Drop a workaround for `pyreadline <https://github.com/pyreadline/pyreadline>`__ that made it work with ``--pdb``.
+
+  The workaround was introduced in `#1281 <https://github.com/pytest-dev/pytest/pull/1281>`__ in 2015, however since then
+  `pyreadline seems to have gone unmaintained <https://github.com/pyreadline/pyreadline/issues/58>`__, is `generating
+  warnings <https://github.com/pytest-dev/pytest/issues/8847>`__, and will stop working on Python 3.10.
+
+
+- `#9061 <https://github.com/pytest-dev/pytest/issues/9061>`_: Using :func:`pytest.approx` in a boolean context now raises an error hinting at the proper usage.
+
+  It is apparently common for users to mistakenly use ``pytest.approx`` like this:
+
+  .. code-block:: python
+
+      assert pytest.approx(actual, expected)
+
+  While the correct usage is:
+
+  .. code-block:: python
+
+      assert actual == pytest.approx(expected)
+
+  The new error message helps catch those mistakes.
+
+
+- `#9277 <https://github.com/pytest-dev/pytest/issues/9277>`_: The ``pytest.Instance`` collector type has been removed.
+  Importing ``pytest.Instance`` or ``_pytest.python.Instance`` returns a dummy type and emits a deprecation warning.
+  See :ref:`instance-collector-deprecation` for details.
+
+
+- `#9308 <https://github.com/pytest-dev/pytest/issues/9308>`_: **PytestRemovedIn7Warning deprecation warnings are now errors by default.**
+
+  Following our plan to remove deprecated features with as little disruption as
+  possible, all warnings of type ``PytestRemovedIn7Warning`` now generate errors
+  instead of warning messages by default.
+
+  **The affected features will be effectively removed in pytest 7.1**, so please consult the
+  :ref:`deprecations` section in the docs for directions on how to update existing code.
+
+  In the pytest ``7.0.X`` series, it is possible to change the errors back into warnings as a
+  stopgap measure by adding this to your ``pytest.ini`` file:
+
+  .. code-block:: ini
+
+      [pytest]
+      filterwarnings =
+          ignore::pytest.PytestRemovedIn7Warning
+
+  But this will stop working when pytest ``7.1`` is released.
+
+  **If you have concerns** about the removal of a specific feature, please add a
+  comment to :issue:`9308`.
+
+
+
+Deprecations
+------------
+
+- `#7259 <https://github.com/pytest-dev/pytest/issues/7259>`_: ``py.path.local`` arguments for hooks have been deprecated. See :ref:`the deprecation note <legacy-path-hooks-deprecated>` for full details.
+
+  ``py.path.local`` arguments to Node constructors have been deprecated. See :ref:`the deprecation note <node-ctor-fspath-deprecation>` for full details.
+
+  .. note::
+      The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
+      new attribute being ``path``) is **the opposite** of the situation for hooks
+      (the old argument being ``path``).
+
+      This is an unfortunate artifact due to historical reasons, which should be
+      resolved in future versions as we slowly get rid of the :pypi:`py`
+      dependency (see :issue:`9283` for a longer discussion).
+
+
+- `#7469 <https://github.com/pytest-dev/pytest/issues/7469>`_: Directly constructing the following classes is now deprecated:
+
+  - ``_pytest.mark.structures.Mark``
+  - ``_pytest.mark.structures.MarkDecorator``
+  - ``_pytest.mark.structures.MarkGenerator``
+  - ``_pytest.python.Metafunc``
+  - ``_pytest.runner.CallInfo``
+  - ``_pytest._code.ExceptionInfo``
+  - ``_pytest.config.argparsing.Parser``
+  - ``_pytest.config.argparsing.OptionGroup``
+  - ``_pytest.pytester.HookRecorder``
+
+  These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8.
+
+
+- `#8242 <https://github.com/pytest-dev/pytest/issues/8242>`_: Raising :class:`unittest.SkipTest` to skip collection of tests during the
+  pytest collection phase is deprecated. Use :func:`pytest.skip` instead.
+
+  Note: This deprecation only relates to using :class:`unittest.SkipTest` during test
+  collection. You are probably not doing that. Ordinary usage of
+  :class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` /
+  :func:`unittest.skip` in unittest test cases is fully supported.
+
+  .. note:: This deprecation has been reverted in pytest 7.1.0.
+
+
+- `#8315 <https://github.com/pytest-dev/pytest/issues/8315>`_: Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
+  scheduled for removal in pytest 8 (deprecated since pytest 2.4.0):
+
+  - ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead.
+  - ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.
+
+
+- `#8447 <https://github.com/pytest-dev/pytest/issues/8447>`_: Defining a custom pytest node type which is both an :class:`~pytest.Item` and a :class:`~pytest.Collector` (e.g. :class:`~pytest.File`) now issues a warning.
+  It was never sanely supported and triggers hard to debug errors.
+
+  See :ref:`the deprecation note <diamond-inheritance-deprecated>` for full details.
+
+
+- `#8592 <https://github.com/pytest-dev/pytest/issues/8592>`_: ``pytest_cmdline_preparse`` has been officially deprecated.  It will be removed in a future release.  Use :hook:`pytest_load_initial_conftests` instead.
+
+  See :ref:`the deprecation note <cmdline-preparse-deprecated>` for full details.
+
+
+- `#8645 <https://github.com/pytest-dev/pytest/issues/8645>`_: :func:`pytest.warns(None) <pytest.warns>` is now deprecated because many people used
+  it to mean "this code does not emit warnings", but it actually had the effect of
+  checking that the code emits at least one warning of any type - like ``pytest.warns()``
+  or ``pytest.warns(Warning)``.
+
+
+- `#8948 <https://github.com/pytest-dev/pytest/issues/8948>`_: :func:`pytest.skip(msg=...) <pytest.skip>`, :func:`pytest.fail(msg=...) <pytest.fail>` and :func:`pytest.exit(msg=...) <pytest.exit>`
+  signatures now accept a ``reason`` argument instead of ``msg``.  Using ``msg`` still works, but is deprecated and will be removed in a future release.
+
+  This was changed for consistency with :func:`pytest.mark.skip <pytest.mark.skip>` and  :func:`pytest.mark.xfail <pytest.mark.xfail>` which both accept
+  ``reason`` as an argument.
+
+- `#8174 <https://github.com/pytest-dev/pytest/issues/8174>`_: The following changes have been made to types reachable through :attr:`pytest.ExceptionInfo.traceback`:
+
+  - The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``.
+  - The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``.
+
+  There was no deprecation period for this change (sorry!).
+
+
+Features
+--------
+
+- `#5196 <https://github.com/pytest-dev/pytest/issues/5196>`_: Tests are now ordered by definition order in more cases.
+
+  In a class hierarchy, tests from base classes are now consistently ordered before tests defined on their subclasses (reverse MRO order).
+
+
+- `#7132 <https://github.com/pytest-dev/pytest/issues/7132>`_: Added two environment variables :envvar:`PYTEST_THEME` and :envvar:`PYTEST_THEME_MODE` to let the users customize the pygments theme used.
+
+
+- `#7259 <https://github.com/pytest-dev/pytest/issues/7259>`_: Added :meth:`cache.mkdir() <pytest.Cache.mkdir>`, which is similar to the existing ``cache.makedir()``,
+  but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``.
+
+  Added a ``paths`` type to :meth:`parser.addini() <pytest.Parser.addini>`,
+  as in ``parser.addini("mypaths", "my paths", type="paths")``,
+  which is similar to the existing ``pathlist``,
+  but returns a list of :class:`pathlib.Path` instead of legacy ``py.path.local``.
+
+
+- `#7469 <https://github.com/pytest-dev/pytest/issues/7469>`_: The types of objects used in pytest's API are now exported so they may be used in type annotations.
+
+  The newly-exported types are:
+
+  - ``pytest.Config`` for :class:`Config <pytest.Config>`.
+  - ``pytest.Mark`` for :class:`marks <pytest.Mark>`.
+  - ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`.
+  - ``pytest.MarkGenerator`` for the :class:`pytest.mark <pytest.MarkGenerator>` singleton.
+  - ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the :hook:`pytest_generate_tests` hook.
+  - ``pytest.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks.
+  - ``pytest.PytestPluginManager`` for :class:`PytestPluginManager <pytest.PytestPluginManager>`.
+  - ``pytest.ExceptionInfo`` for the :class:`ExceptionInfo <pytest.ExceptionInfo>` type returned from :func:`pytest.raises` and passed to various hooks.
+  - ``pytest.Parser`` for the :class:`Parser <pytest.Parser>` type passed to the :hook:`pytest_addoption` hook.
+  - ``pytest.OptionGroup`` for the :class:`OptionGroup <pytest.OptionGroup>` type returned from the :func:`parser.addgroup <pytest.Parser.getgroup>` method.
+  - ``pytest.HookRecorder`` for the :class:`HookRecorder <pytest.HookRecorder>` type returned from :class:`~pytest.Pytester`.
+  - ``pytest.RecordedHookCall`` for the :class:`RecordedHookCall <pytest.HookRecorder>` type returned from :class:`~pytest.HookRecorder`.
+  - ``pytest.RunResult`` for the :class:`RunResult <pytest.RunResult>` type returned from :class:`~pytest.Pytester`.
+  - ``pytest.LineMatcher`` for the :class:`LineMatcher <pytest.LineMatcher>` type used in :class:`~pytest.RunResult` and others.
+  - ``pytest.TestReport`` for the :class:`TestReport <pytest.TestReport>` type used in various hooks.
+  - ``pytest.CollectReport`` for the :class:`CollectReport <pytest.CollectReport>` type used in various hooks.
+
+  Constructing most of them directly is not supported; they are only meant for use in type annotations.
+  Doing so will emit a deprecation warning, and may become a hard-error in pytest 8.0.
+
+  Subclassing them is also not supported. This is not currently enforced at runtime, but is detected by type-checkers such as mypy.
+
+
+- `#7856 <https://github.com/pytest-dev/pytest/issues/7856>`_: :ref:`--import-mode=importlib <import-modes>` now works with features that
+  depend on modules being on :py:data:`sys.modules`, such as :mod:`pickle` and :mod:`dataclasses`.
+
+
+- `#8144 <https://github.com/pytest-dev/pytest/issues/8144>`_: The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument:
+
+  - :hook:`pytest_ignore_collect` - The ``collection_path`` parameter (equivalent to existing ``path`` parameter).
+  - :hook:`pytest_collect_file` - The ``file_path`` parameter (equivalent to existing ``path`` parameter).
+  - :hook:`pytest_pycollect_makemodule` - The ``module_path`` parameter (equivalent to existing ``path`` parameter).
+  - :hook:`pytest_report_header` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
+  - :hook:`pytest_report_collectionfinish` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
+
+  .. note::
+      The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
+      new attribute being ``path``) is **the opposite** of the situation for hooks
+      (the old argument being ``path``).
+
+      This is an unfortunate artifact due to historical reasons, which should be
+      resolved in future versions as we slowly get rid of the :pypi:`py`
+      dependency (see :issue:`9283` for a longer discussion).
+
+
+- `#8251 <https://github.com/pytest-dev/pytest/issues/8251>`_: Implement ``Node.path`` as a ``pathlib.Path``. Both the old ``fspath`` and this new attribute gets set no matter whether ``path`` or ``fspath`` (deprecated) is passed to the constructor. It is a replacement for the ``fspath`` attribute (which represents the same path as ``py.path.local``). While ``fspath`` is not deprecated yet
+  due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`, we expect to deprecate it in a future release.
+
+  .. note::
+      The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
+      new attribute being ``path``) is **the opposite** of the situation for hooks
+      (the old argument being ``path``).
+
+      This is an unfortunate artifact due to historical reasons, which should be
+      resolved in future versions as we slowly get rid of the :pypi:`py`
+      dependency (see :issue:`9283` for a longer discussion).
+
+
+- `#8421 <https://github.com/pytest-dev/pytest/issues/8421>`_: :func:`pytest.approx` now works on :class:`~decimal.Decimal` within mappings/dicts and sequences/lists.
+
+
+- `#8606 <https://github.com/pytest-dev/pytest/issues/8606>`_: pytest invocations with ``--fixtures-per-test`` and ``--fixtures`` have been enriched with:
+
+  - Fixture location path printed with the fixture name.
+  - First section of the fixture's docstring printed under the fixture name.
+  - Whole of fixture's docstring printed under the fixture name using ``--verbose`` option.
+
+
+- `#8761 <https://github.com/pytest-dev/pytest/issues/8761>`_: New :ref:`version-tuple` attribute, which makes it simpler for users to do something depending on the pytest version (such as declaring hooks which are introduced in later versions).
+
+
+- `#8789 <https://github.com/pytest-dev/pytest/issues/8789>`_: Switch TOML parser from ``toml`` to ``tomli`` for TOML v1.0.0 support in ``pyproject.toml``.
+
+
+- `#8920 <https://github.com/pytest-dev/pytest/issues/8920>`_: Added :class:`pytest.Stash`, a facility for plugins to store their data on :class:`~pytest.Config` and :class:`~_pytest.nodes.Node`\s in a type-safe and conflict-free manner.
+  See :ref:`plugin-stash` for details.
+
+
+- `#8953 <https://github.com/pytest-dev/pytest/issues/8953>`_: :class:`~pytest.RunResult` method :meth:`~pytest.RunResult.assert_outcomes` now accepts a
+  ``warnings`` argument to assert the total number of warnings captured.
+
+
+- `#8954 <https://github.com/pytest-dev/pytest/issues/8954>`_: ``--debug`` flag now accepts a :class:`str` file to route debug logs into, remains defaulted to `pytestdebug.log`.
+
+
+- `#9023 <https://github.com/pytest-dev/pytest/issues/9023>`_: Full diffs are now always shown for equality assertions of iterables when
+  `CI` or ``BUILD_NUMBER`` is found in the environment, even when ``-v`` isn't
+  used.
+
+
+- `#9113 <https://github.com/pytest-dev/pytest/issues/9113>`_: :class:`~pytest.RunResult` method :meth:`~pytest.RunResult.assert_outcomes` now accepts a
+  ``deselected`` argument to assert the total number of deselected tests.
+
+
+- `#9114 <https://github.com/pytest-dev/pytest/issues/9114>`_: Added :confval:`pythonpath` setting that adds listed paths to :data:`sys.path` for the duration of the test session. If you currently use the pytest-pythonpath or pytest-srcpaths plugins, you should be able to replace them with built-in `pythonpath` setting.
+
+
+
+Improvements
+------------
+
+- `#7480 <https://github.com/pytest-dev/pytest/issues/7480>`_: A deprecation scheduled to be removed in a major version X (e.g. pytest 7, 8, 9, ...) now uses warning category `PytestRemovedInXWarning`,
+  a subclass of :class:`~pytest.PytestDeprecationWarning`,
+  instead of :class:`~pytest.PytestDeprecationWarning` directly.
+
+  See :ref:`backwards-compatibility` for more details.
+
+
+- `#7864 <https://github.com/pytest-dev/pytest/issues/7864>`_: Improved error messages when parsing warning filters.
+
+  Previously pytest would show an internal traceback, which besides being ugly sometimes would hide the cause
+  of the problem (for example an ``ImportError`` while importing a specific warning type).
+
+
+- `#8335 <https://github.com/pytest-dev/pytest/issues/8335>`_: Improved :func:`pytest.approx` assertion messages for sequences of numbers.
+
+  The assertion messages now dumps a table with the index and the error of each diff.
+  Example::
+
+      >       assert [1, 2, 3, 4] == pytest.approx([1, 3, 3, 5])
+      E       assert comparison failed for 2 values:
+      E         Index | Obtained | Expected
+      E         1     | 2        | 3 +- 3.0e-06
+      E         3     | 4        | 5 +- 5.0e-06
+
+
+- `#8403 <https://github.com/pytest-dev/pytest/issues/8403>`_: By default, pytest will truncate long strings in assert errors so they don't clutter the output too much,
+  currently at ``240`` characters by default.
+
+  However, in some cases the longer output helps, or is even crucial, to diagnose a failure. Using ``-v`` will
+  now increase the truncation threshold to ``2400`` characters, and ``-vv`` or higher will disable truncation entirely.
+
+
+- `#8509 <https://github.com/pytest-dev/pytest/issues/8509>`_: Fixed issue where :meth:`unittest.TestCase.setUpClass` is not called when a test has `/` in its name since pytest 6.2.0.
+
+  This refers to the path part in pytest node IDs, e.g. ``TestClass::test_it`` in the node ID ``tests/test_file.py::TestClass::test_it``.
+
+  Now, instead of assuming that the test name does not contain ``/``, it is assumed that test path does not contain ``::``. We plan to hopefully make both of these work in the future.
+
+
+- `#8803 <https://github.com/pytest-dev/pytest/issues/8803>`_: It is now possible to add colors to custom log levels on cli log.
+
+  By using ``add_color_level`` from a :hook:`pytest_configure` hook, colors can be added::
+
+      logging_plugin = config.pluginmanager.get_plugin('logging-plugin')
+      logging_plugin.log_cli_handler.formatter.add_color_level(logging.INFO, 'cyan')
+      logging_plugin.log_cli_handler.formatter.add_color_level(logging.SPAM, 'blue')
+
+  See :ref:`log_colors` for more information.
+
+
+- `#8822 <https://github.com/pytest-dev/pytest/issues/8822>`_: When showing fixture paths in `--fixtures` or `--fixtures-by-test`, fixtures coming from pytest itself now display an elided path, rather than the full path to the file in the `site-packages` directory.
+
+
+- `#8898 <https://github.com/pytest-dev/pytest/issues/8898>`_: Complex numbers are now treated like floats and integers when generating parameterization IDs.
+
+
+- `#9062 <https://github.com/pytest-dev/pytest/issues/9062>`_: ``--stepwise-skip`` now implicitly enables ``--stepwise`` and can be used on its own.
+
+
+- `#9205 <https://github.com/pytest-dev/pytest/issues/9205>`_: :meth:`pytest.Cache.set` now preserves key order when saving dicts.
+
+
+
+Bug Fixes
+---------
+
+- `#7124 <https://github.com/pytest-dev/pytest/issues/7124>`_: Fixed an issue where ``__main__.py`` would raise an ``ImportError`` when ``--doctest-modules`` was provided.
+
+
+- `#8061 <https://github.com/pytest-dev/pytest/issues/8061>`_: Fixed failing ``staticmethod`` test cases if they are inherited from a parent test class.
+
+
+- `#8192 <https://github.com/pytest-dev/pytest/issues/8192>`_: ``testdir.makefile`` now silently accepts values which don't start with ``.`` to maintain backward compatibility with older pytest versions.
+
+  ``pytester.makefile`` now issues a clearer error if the ``.`` is missing in the ``ext`` argument.
+
+
+- `#8258 <https://github.com/pytest-dev/pytest/issues/8258>`_: Fixed issue where pytest's ``faulthandler`` support would not dump traceback on crashes
+  if the :mod:`faulthandler` module was already enabled during pytest startup (using
+  ``python -X dev -m pytest`` for example).
+
+
+- `#8317 <https://github.com/pytest-dev/pytest/issues/8317>`_: Fixed an issue where illegal directory characters derived from ``getpass.getuser()`` raised an ``OSError``.
+
+
+- `#8367 <https://github.com/pytest-dev/pytest/issues/8367>`_: Fix ``Class.from_parent`` so it forwards extra keyword arguments to the constructor.
+
+
+- `#8377 <https://github.com/pytest-dev/pytest/issues/8377>`_: The test selection options ``pytest -k`` and ``pytest -m`` now support matching
+  names containing forward slash (``/``) characters.
+
+
+- `#8384 <https://github.com/pytest-dev/pytest/issues/8384>`_: The ``@pytest.mark.skip`` decorator now correctly handles its arguments. When the ``reason`` argument is accidentally given both positional and as a keyword (e.g. because it was confused with ``skipif``), a ``TypeError`` now occurs. Before, such tests were silently skipped, and the positional argument ignored. Additionally, ``reason`` is now documented correctly as positional or keyword (rather than keyword-only).
+
+
+- `#8394 <https://github.com/pytest-dev/pytest/issues/8394>`_: Use private names for internal fixtures that handle classic setup/teardown so that they don't show up with the default ``--fixtures`` invocation (but they still show up with ``--fixtures -v``).
+
+
+- `#8456 <https://github.com/pytest-dev/pytest/issues/8456>`_: The :confval:`required_plugins` config option now works correctly when pre-releases of plugins are installed, rather than falsely claiming that those plugins aren't installed at all.
+
+
+- `#8464 <https://github.com/pytest-dev/pytest/issues/8464>`_: ``-c <config file>`` now also properly defines ``rootdir`` as the directory that contains ``<config file>``.
+
+
+- `#8503 <https://github.com/pytest-dev/pytest/issues/8503>`_: :meth:`pytest.MonkeyPatch.syspath_prepend` no longer fails when
+  ``setuptools`` is not installed.
+  It now only calls ``pkg_resources.fixup_namespace_packages`` if
+  ``pkg_resources`` was previously imported, because it is not needed otherwise.
+
+
+- `#8548 <https://github.com/pytest-dev/pytest/issues/8548>`_: Introduce fix to handle precision width in ``log-cli-format`` in turn to fix output coloring for certain formats.
+
+
+- `#8796 <https://github.com/pytest-dev/pytest/issues/8796>`_: Fixed internal error when skipping doctests.
+
+
+- `#8983 <https://github.com/pytest-dev/pytest/issues/8983>`_: The test selection options ``pytest -k`` and ``pytest -m`` now support matching names containing backslash (`\\`) characters.
+  Backslashes are treated literally, not as escape characters (the values being matched against are already escaped).
+
+
+- `#8990 <https://github.com/pytest-dev/pytest/issues/8990>`_: Fix `pytest -vv` crashing with an internal exception `AttributeError: 'str' object has no attribute 'relative_to'` in some cases.
+
+
+- `#9077 <https://github.com/pytest-dev/pytest/issues/9077>`_: Fixed confusing error message when ``request.fspath`` / ``request.path`` was accessed from a session-scoped fixture.
+
+
+- `#9131 <https://github.com/pytest-dev/pytest/issues/9131>`_: Fixed the URL used by ``--pastebin`` to use `bpa.st <http://bpa.st>`__.
+
+
+- `#9163 <https://github.com/pytest-dev/pytest/issues/9163>`_: The end line number and end column offset are now properly set for rewritten assert statements.
+
+
+- `#9169 <https://github.com/pytest-dev/pytest/issues/9169>`_: Support for the ``files`` API from ``importlib.resources`` within rewritten files.
+
+
+- `#9272 <https://github.com/pytest-dev/pytest/issues/9272>`_: The nose compatibility module-level fixtures `setup()` and `teardown()` are now only called once per module, instead of for each test function.
+  They are now called even if object-level `setup`/`teardown` is defined.
+
+
+
+Improved Documentation
+----------------------
+
+- `#4320 <https://github.com/pytest-dev/pytest/issues/4320>`_: Improved docs for `pytester.copy_example`.
+
+
+- `#5105 <https://github.com/pytest-dev/pytest/issues/5105>`_: Add automatically generated :ref:`plugin-list`. The list is updated on a periodic schedule.
+
+
+- `#8337 <https://github.com/pytest-dev/pytest/issues/8337>`_: Recommend `numpy.testing <https://numpy.org/doc/stable/reference/routines.testing.html>`__ module on :func:`pytest.approx` documentation.
+
+
+- `#8655 <https://github.com/pytest-dev/pytest/issues/8655>`_: Help text for ``--pdbcls`` more accurately reflects the option's behavior.
+
+
+- `#9210 <https://github.com/pytest-dev/pytest/issues/9210>`_: Remove incorrect docs about ``confcutdir`` being a configuration option: it can only be set through the ``--confcutdir`` command-line option.
+
+
+- `#9242 <https://github.com/pytest-dev/pytest/issues/9242>`_: Upgrade readthedocs configuration to use a `newer Ubuntu version <https://blog.readthedocs.com/new-build-specification/>`__` with better unicode support for PDF docs.
+
+
+- `#9341 <https://github.com/pytest-dev/pytest/issues/9341>`_: Various methods commonly used for :ref:`non-python tests` are now correctly documented in the reference docs. They were undocumented previously.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#8133 <https://github.com/pytest-dev/pytest/issues/8133>`_: Migrate to ``setuptools_scm`` 6.x to use ``SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST`` for more robust release tooling.
+
+
+- `#8174 <https://github.com/pytest-dev/pytest/issues/8174>`_: The following changes have been made to internal pytest types/functions:
+
+  - The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``.
+  - The ``_pytest.python.path_matches_patterns()`` function takes ``Path`` instead of ``py.path.local``.
+  - The ``_pytest._code.Traceback.cut()`` function accepts any ``os.PathLike[str]``, not just ``py.path.local``.
+
+
+- `#8248 <https://github.com/pytest-dev/pytest/issues/8248>`_: Internal Restructure: let ``python.PyObjMixin`` inherit from ``nodes.Node`` to carry over typing information.
+
+
+- `#8432 <https://github.com/pytest-dev/pytest/issues/8432>`_: Improve error message when :func:`pytest.skip` is used at module level without passing `allow_module_level=True`.
+
+
+- `#8818 <https://github.com/pytest-dev/pytest/issues/8818>`_: Ensure ``regendoc`` opts out of ``TOX_ENV`` cachedir selection to ensure independent example test runs.
+
+
+- `#8913 <https://github.com/pytest-dev/pytest/issues/8913>`_: The private ``CallSpec2._arg2scopenum`` attribute has been removed after an internal refactoring.
+
+
+- `#8967 <https://github.com/pytest-dev/pytest/issues/8967>`_: :hook:`pytest_assertion_pass` is no longer considered experimental and
+  future changes to it will be considered more carefully.
+
+
+- `#9202 <https://github.com/pytest-dev/pytest/issues/9202>`_: Add github action to upload coverage report to codecov instead of bash uploader.
+
+
+- `#9225 <https://github.com/pytest-dev/pytest/issues/9225>`_: Changed the command used to create sdist and wheel artifacts: using the build package instead of setup.py.
+
+
+- `#9351 <https://github.com/pytest-dev/pytest/issues/9351>`_: Correct minor typos in doc/en/example/special.rst.
+
+
 pytest 6.2.5 (2021-08-29)
 =========================
 
@@ -62,7 +2429,7 @@ Bug Fixes
   the ``tmp_path``/``tmpdir`` fixture). Now the directories are created with
   private permissions.
 
-  pytest used to silenty use a pre-existing ``/tmp/pytest-of-<username>`` directory,
+  pytest used to silently use a preexisting ``/tmp/pytest-of-<username>`` directory,
   even if owned by another user. This means another user could pre-create such a
   directory and gain control of another user's temporary directory. Now such a
   condition results in an error.
@@ -158,7 +2525,7 @@ Features
 
   This is part of the movement to use :class:`pathlib.Path` objects internally, in order to remove the dependency to ``py`` in the future.
 
-  Internally, the old :class:`Testdir <_pytest.pytester.Testdir>` is now a thin wrapper around :class:`Pytester <_pytest.pytester.Pytester>`, preserving the old interface.
+  Internally, the old ``pytest.Testdir`` is now a thin wrapper around :class:`~pytest.Pytester`, preserving the old interface.
 
 
 - :issue:`7695`: A new hook was added, `pytest_markeval_namespace` which should return a dictionary.
@@ -196,7 +2563,7 @@ Features
 Improvements
 ------------
 
-- :issue:`1265`: Added an ``__str__`` implementation to the :class:`~pytest.pytester.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method.
+- :issue:`1265`: Added an ``__str__`` implementation to the :class:`~pytest.LineMatcher` class which is returned from ``pytester.run_pytest().stdout`` and similar. It returns the entire output, like the existing ``str()`` method.
 
 
 - :issue:`2044`: Verbose mode now shows the reason that a test was skipped in the test's terminal line after the "SKIPPED", "XFAIL" or "XPASS".
@@ -260,7 +2627,7 @@ Bug Fixes
 - :issue:`7911`: Directories created by by :fixture:`tmp_path` and :fixture:`tmpdir` are now considered stale after 3 days without modification (previous value was 3 hours) to avoid deleting directories still in use in long running test suites.
 
 
-- :issue:`7913`: Fixed a crash or hang in :meth:`pytester.spawn <_pytest.pytester.Pytester.spawn>` when the :mod:`readline` module is involved.
+- :issue:`7913`: Fixed a crash or hang in :meth:`pytester.spawn <pytest.Pytester.spawn>` when the :mod:`readline` module is involved.
 
 
 - :issue:`7951`: Fixed handling of recursive symlinks when collecting tests.
@@ -377,8 +2744,8 @@ Deprecations
   if you use this and want a replacement.
 
 
-- :issue:`7255`: The :func:`pytest_warning_captured <_pytest.hookspec.pytest_warning_captured>` hook is deprecated in favor
-  of :func:`pytest_warning_recorded <_pytest.hookspec.pytest_warning_recorded>`, and will be removed in a future version.
+- :issue:`7255`: The ``pytest_warning_captured`` hook is deprecated in favor
+  of :hook:`pytest_warning_recorded`, and will be removed in a future version.
 
 
 - :issue:`7648`: The ``gethookproxy()`` and ``isinitpath()`` methods of ``FSCollector`` and ``Package`` are deprecated;
@@ -405,8 +2772,8 @@ Improvements
 - :issue:`7572`: When a plugin listed in ``required_plugins`` is missing or an unknown config key is used with ``--strict-config``, a simple error message is now shown instead of a stacktrace.
 
 
-- :issue:`7685`: Added two new attributes :attr:`rootpath <_pytest.config.Config.rootpath>` and :attr:`inipath <_pytest.config.Config.inipath>` to :class:`Config <_pytest.config.Config>`.
-  These attributes are :class:`pathlib.Path` versions of the existing :attr:`rootdir <_pytest.config.Config.rootdir>` and :attr:`inifile <_pytest.config.Config.inifile>` attributes,
+- :issue:`7685`: Added two new attributes :attr:`rootpath <pytest.Config.rootpath>` and :attr:`inipath <pytest.Config.inipath>` to :class:`~pytest.Config`.
+  These attributes are :class:`pathlib.Path` versions of the existing ``rootdir`` and ``inifile`` attributes,
   and should be preferred over them when possible.
 
 
@@ -477,7 +2844,7 @@ Trivial/Internal Changes
 - :issue:`7587`: The dependency on the ``more-itertools`` package has been removed.
 
 
-- :issue:`7631`: The result type of :meth:`capfd.readouterr() <_pytest.capture.CaptureFixture.readouterr>` (and similar) is no longer a namedtuple,
+- :issue:`7631`: The result type of :meth:`capfd.readouterr() <pytest.CaptureFixture.readouterr>` (and similar) is no longer a namedtuple,
   but should behave like one in all respects. This was done for technical reasons.
 
 
@@ -486,7 +2853,7 @@ Trivial/Internal Changes
   process, pytest now ignores builtin attributes (like ``__class__``,
   ``__delattr__`` and ``__new__``) without consulting the :confval:`python_classes` and
   :confval:`python_functions` configuration options and without passing them to plugins
-  using the :func:`pytest_pycollect_makeitem <_pytest.hookspec.pytest_pycollect_makeitem>` hook.
+  using the :hook:`pytest_pycollect_makeitem` hook.
 
 
 pytest 6.0.2 (2020-09-04)
@@ -628,7 +2995,7 @@ Breaking Changes
   Resolving symlinks for the current directory and during collection was introduced as a bugfix in 3.9.0, but it actually is a new feature which had unfortunate consequences in Windows and surprising results in other platforms.
 
   The team decided to step back on resolving symlinks at all, planning to review this in the future with a more solid solution (see discussion in
-  :pull:`6523` for details).
+  :pr:`6523` for details).
 
   This might break test suites which made use of this feature; the fix is to create a symlink
   for the entire test tree, and not only to partial files/tress as it was possible previously.
@@ -789,7 +3156,7 @@ Features
   also changes ``sys.modules`` as a side-effect), which works but has a number of drawbacks, like requiring test modules
   that don't live in packages to have unique names (as they need to reside under a unique name in ``sys.modules``).
 
-  ``--import-mode=importlib`` uses more fine grained import mechanisms from ``importlib`` which don't
+  ``--import-mode=importlib`` uses more fine-grained import mechanisms from ``importlib`` which don't
   require pytest to change ``sys.path`` or ``sys.modules`` at all, eliminating much of the drawbacks
   of the previous mode.
 
@@ -806,7 +3173,7 @@ Improvements
 ------------
 
 - :issue:`4375`: The ``pytest`` command now suppresses the ``BrokenPipeError`` error message that
-  is printed to stderr when the output of ``pytest`` is piped and and the pipe is
+  is printed to stderr when the output of ``pytest`` is piped and the pipe is
   closed by the piped-to program (common examples are ``less`` and ``head``).
 
 
@@ -828,7 +3195,7 @@ Improvements
   is not displayed by default for passing tests. This change makes the mistake
   visible during testing.
 
-  You may supress this behavior temporarily or permanently by setting
+  You may suppress this behavior temporarily or permanently by setting
   ``logging.raiseExceptions = False``.
 
 
@@ -855,10 +3222,10 @@ Improvements
 - :issue:`7128`: `pytest --version` now displays just the pytest version, while `pytest --version --version` displays more verbose information including plugins. This is more consistent with how other tools show `--version`.
 
 
-- :issue:`7133`: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` will now override any :confval:`log_level` set via the CLI or configuration file.
+- :issue:`7133`: :meth:`caplog.set_level() <pytest.LogCaptureFixture.set_level>` will now override any :confval:`log_level` set via the CLI or configuration file.
 
 
-- :issue:`7159`: :meth:`caplog.set_level() <_pytest.logging.LogCaptureFixture.set_level>` and :meth:`caplog.at_level() <_pytest.logging.LogCaptureFixture.at_level>` no longer affect
+- :issue:`7159`: :meth:`caplog.set_level() <pytest.LogCaptureFixture.set_level>` and :meth:`caplog.at_level() <pytest.LogCaptureFixture.at_level>` no longer affect
   the level of logs that are shown in the *Captured log report* report section.
 
 
@@ -911,7 +3278,7 @@ Bug Fixes
 - :issue:`6871`: Fix crash with captured output when using :fixture:`capsysbinary`.
 
 
-- :issue:`6909`: Revert the change introduced by :pull:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
+- :issue:`6909`: Revert the change introduced by :pr:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
 
   The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted.
 
@@ -953,7 +3320,7 @@ Bug Fixes
   parameter when Python is called with the ``-bb`` flag.
 
 
-- :issue:`7143`: Fix :meth:`pytest.File.from_parent` so it forwards extra keyword arguments to the constructor.
+- :issue:`7143`: Fix :meth:`pytest.File.from_parent <_pytest.nodes.Node.from_parent>` so it forwards extra keyword arguments to the constructor.
 
 
 - :issue:`7145`: Classes with broken ``__getattribute__`` methods are displayed correctly during failures.
@@ -1081,7 +3448,7 @@ pytest 5.4.1 (2020-03-13)
 Bug Fixes
 ---------
 
-- :issue:`6909`: Revert the change introduced by :pull:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
+- :issue:`6909`: Revert the change introduced by :pr:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature.
 
   The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted.
 
@@ -1108,7 +3475,7 @@ Breaking Changes
   This hook has been marked as deprecated and not been even called by pytest for over 10 years now.
 
 
-- :issue:`6673`: Reversed / fix meaning of "+/-" in error diffs.  "-" means that sth. expected is missing in the result and "+" means that there are unexpected extras in the result.
+- :issue:`6673`: Reversed / fix meaning of "+/-" in error diffs.  "-" means that something expected is missing in the result and "+" means that there are unexpected extras in the result.
 
 
 - :issue:`6737`: The ``cached_result`` attribute of ``FixtureDef`` is now set to ``None`` when
@@ -1204,7 +3571,7 @@ Improvements
 - :issue:`6384`: Make `--showlocals` work also with `--tb=short`.
 
 
-- :issue:`6653`: Add support for matching lines consecutively with :attr:`LineMatcher <_pytest.pytester.LineMatcher>`'s :func:`~_pytest.pytester.LineMatcher.fnmatch_lines` and :func:`~_pytest.pytester.LineMatcher.re_match_lines`.
+- :issue:`6653`: Add support for matching lines consecutively with :class:`~pytest.LineMatcher`'s :func:`~pytest.LineMatcher.fnmatch_lines` and :func:`~pytest.LineMatcher.re_match_lines`.
 
 
 - :issue:`6658`: Code is now highlighted in tracebacks when ``pygments`` is installed.
@@ -1272,10 +3639,10 @@ Bug Fixes
 - :issue:`6597`: Fix node ids which contain a parametrized empty-string variable.
 
 
-- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest <_pytest.pytester.Testdir.runpytest>` etc.
+- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's ``testdir.runpytest`` etc.
 
 
-- :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :func:`pytest_sessionfinish <_pytest.hookspec.pytest_sessionfinish>` hook.  This includes quitting from a debugger.
+- :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :hook:`pytest_sessionfinish` hook.  This includes quitting from a debugger.
 
 
 - :issue:`6752`: When :py:func:`pytest.raises` is used as a function (as opposed to a context manager),
@@ -1283,7 +3650,7 @@ Bug Fixes
   it was swallowed and ignored (regression in pytest 5.1.0).
 
 
-- :issue:`6801`: Do not display empty lines inbetween traceback for unexpected exceptions with doctests.
+- :issue:`6801`: Do not display empty lines in between traceback for unexpected exceptions with doctests.
 
 
 - :issue:`6802`: The :fixture:`testdir fixture <testdir>` works within doctests now.
@@ -1338,7 +3705,7 @@ Bug Fixes
   ``multiprocessing`` module.
 
 
-- :issue:`6436`: :class:`FixtureDef <_pytest.fixtures.FixtureDef>` objects now properly register their finalizers with autouse and
+- :issue:`6436`: :class:`~pytest.FixtureDef` objects now properly register their finalizers with autouse and
   parameterized fixtures that execute before them in the fixture stack so they are torn
   down at the right times, and in the right order.
 
@@ -1387,17 +3754,19 @@ Improvements
 - :issue:`6231`: Improve check for misspelling of :ref:`pytest.mark.parametrize ref`.
 
 
-- :issue:`6257`: Handle :py:func:`pytest.exit` being used via :py:func:`~_pytest.hookspec.pytest_internalerror`, e.g. when quitting pdb from post mortem.
+- :issue:`6257`: Handle :func:`pytest.exit` being used via :hook:`pytest_internalerror`, e.g. when quitting pdb from post mortem.
 
 
 
 Bug Fixes
 ---------
 
-- :issue:`5914`: pytester: fix :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` when used after positive matching.
+- :issue:`5914`: pytester: fix :py:func:`~pytest.LineMatcher.no_fnmatch_line` when used after positive matching.
 
 
-- :issue:`6082`: Fix line detection for doctest samples inside :py:class:`python:property` docstrings, as a workaround to :bpo:`17446`.
+- :issue:`6082`: Fix line detection for doctest samples inside
+  :py:class:`python:property` docstrings, as a workaround to
+  :issue:`python/cpython#61648`.
 
 
 - :issue:`6254`: Fix compatibility with pytest-parallel (regression in pytest 5.3.0).
@@ -1458,8 +3827,8 @@ Features
   rather than implicitly.
 
 
-- :issue:`5914`: :fixture:`testdir` learned two new functions, :py:func:`~_pytest.pytester.LineMatcher.no_fnmatch_line` and
-  :py:func:`~_pytest.pytester.LineMatcher.no_re_match_line`.
+- :issue:`5914`: :fixture:`testdir` learned two new functions, :py:func:`~pytest.LineMatcher.no_fnmatch_line` and
+  :py:func:`~pytest.LineMatcher.no_re_match_line`.
 
   The functions are used to ensure the captured text *does not* match the given
   pattern.
@@ -1945,7 +4314,8 @@ Important
 
 This release is a Python3.5+ only release.
 
-For more details, see our :std:doc:`Python 2.7 and 3.4 support plan <py27-py34-deprecation>`.
+For more details, see our `Python 2.7 and 3.4 support plan
+<https://docs.pytest.org/en/7.0.x/py27-py34-deprecation.html>`_.
 
 Removals
 --------
@@ -2021,7 +4391,7 @@ Deprecations
 Features
 --------
 
-- :issue:`3457`: New :func:`~_pytest.hookspec.pytest_assertion_pass`
+- :issue:`3457`: New :hook:`pytest_assertion_pass`
   hook, called with context information when an assertion *passes*.
 
   This hook is still **experimental** so use it with caution.
@@ -2103,7 +4473,7 @@ Bug Fixes
   (``--collect-only``) when ``--log-cli-level`` is used.
 
 
-- :issue:`5389`: Fix regressions of :pull:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
+- :issue:`5389`: Fix regressions of :pr:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
 
 
 - :issue:`5390`: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods.
@@ -2169,7 +4539,11 @@ Features
 
 - :issue:`6870`: New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.
 
-  Remark: while this is technically a new feature and according to our :ref:`policy <what goes into 4.6.x releases>` it should not have been backported, we have opened an exception in this particular case because it fixes a serious interaction with ``pytest-xdist``, so it can also be considered a bugfix.
+  Remark: while this is technically a new feature and according to our
+  `policy <https://docs.pytest.org/en/7.0.x/py27-py34-deprecation.html#what-goes-into-4-6-x-releases>`_
+  it should not have been backported, we have opened an exception in this
+  particular case because it fixes a serious interaction with ``pytest-xdist``,
+  so it can also be considered a bugfix.
 
 Trivial/Internal Changes
 ------------------------
@@ -2300,7 +4674,7 @@ Bug Fixes
   (``--collect-only``) when ``--log-cli-level`` is used.
 
 
-- :issue:`5389`: Fix regressions of :pull:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
+- :issue:`5389`: Fix regressions of :pr:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``.
 
 
 - :issue:`5390`: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods.
@@ -2341,7 +4715,8 @@ Important
 
 The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**.
 
-For more details, see our :std:doc:`Python 2.7 and 3.4 support plan <py27-py34-deprecation>`.
+For more details, see our `Python 2.7 and 3.4 support plan
+<https://docs.pytest.org/en/7.0.x/py27-py34-deprecation.html>`_.
 
 
 Features
@@ -2707,7 +5082,7 @@ Bug Fixes
 Improved Documentation
 ----------------------
 
-- :issue:`4974`: Update docs for ``pytest_cmdline_parse`` hook to note availability liminations
+- :issue:`4974`: Update docs for ``pytest_cmdline_parse`` hook to note availability limitations
 
 
 
@@ -3048,7 +5423,7 @@ Removals
   See our :ref:`docs <calling fixtures directly deprecated>` on information on how to update your code.
 
 
-- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check.
+- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than an existence check.
 
   Use ``Node.get_closest_marker(name)`` as a replacement.
 
@@ -4152,7 +6527,7 @@ Bug Fixes
 - Fixed a bug where stdout and stderr were logged twice by junitxml when a test
   was marked xfail. (:issue:`3491`)
 
-- Fix ``usefixtures`` mark applyed to unittest tests by correctly instantiating
+- Fix ``usefixtures`` mark applied to unittest tests by correctly instantiating
   ``FixtureInfo``. (:issue:`3498`)
 
 - Fix assertion rewriter compatibility with libraries that monkey patch
@@ -4541,9 +6916,9 @@ Features
 - Console output falls back to "classic" mode when capturing is disabled (``-s``),
   otherwise the output gets garbled to the point of being useless. (:issue:`3038`)
 
-- New :func:`~_pytest.hookspec.pytest_runtest_logfinish`
+- New :hook:`pytest_runtest_logfinish`
   hook which is called when a test item has finished executing, analogous to
-  :func:`~_pytest.hookspec.pytest_runtest_logstart`.
+  :hook:`pytest_runtest_logstart`.
   (:issue:`3101`)
 
 - Improve performance when collecting tests using many fixtures. (:issue:`3107`)
@@ -4565,7 +6940,7 @@ Features
 Bug Fixes
 ---------
 
-- Fix hanging pexpect test on MacOS by using flush() instead of wait().
+- Fix hanging pexpect test on macOS by using flush() instead of wait().
   (:issue:`2022`)
 
 - Fix restoring Python state after in-process pytest runs with the
@@ -4613,7 +6988,7 @@ Trivial/Internal Changes
 ------------------------
 
 - Show a simple and easy error when keyword expressions trigger a syntax error
-  (for example, ``"-k foo and import"`` will show an error that you can not use
+  (for example, ``"-k foo and import"`` will show an error that you cannot use
   the ``import`` keyword in expressions). (:issue:`2953`)
 
 - Change parametrized automatic test id generation to use the ``__name__``
@@ -5260,10 +7635,10 @@ New Features
 * Added ``junit_suite_name`` ini option to specify root ``<testsuite>`` name for JUnit XML reports (:issue:`533`).
 
 * Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files.
-  Thanks :user:`wheerd` for the PR (:pull:`2101`).
+  Thanks :user:`wheerd` for the PR (:pr:`2101`).
 
 * ``pytest.warns`` now checks for subclass relationship rather than
-  class equality. Thanks :user:`lesteve` for the PR (:pull:`2166`)
+  class equality. Thanks :user:`lesteve` for the PR (:pr:`2166`)
 
 * ``pytest.raises`` now asserts that the error message matches a text or regex
   with the ``match`` keyword argument. Thanks :user:`Kriechi` for the PR.
@@ -5291,7 +7666,7 @@ Changes
   the failure. (:issue:`2228`) Thanks to :user:`kkoukiou` for the PR.
 
 * Testcase reports with a ``url`` attribute will now properly write this to junitxml.
-  Thanks :user:`fushi` for the PR (:pull:`1874`).
+  Thanks :user:`fushi` for the PR (:pr:`1874`).
 
 * Remove common items from dict comparison output when verbosity=1. Also update
   the truncation message to make it clearer that pytest truncates all
@@ -5300,12 +7675,12 @@ Changes
 
 * ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use
   ``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks :user:`davidszotten` for
-  the PR (:pull:`1952`).
+  the PR (:pr:`1952`).
 
 * fix :issue:`2013`: turn RecordedWarning into ``namedtuple``,
   to give it a comprehensible repr while preventing unwarranted modification.
 
-* fix :issue:`2208`: ensure an iteration limit for _pytest.compat.get_real_func.
+* fix :issue:`2208`: ensure an iteration limit for ``_pytest.compat.get_real_func``.
   Thanks :user:`RonnyPfannschmidt` for the report and PR.
 
 * Hooks are now verified after collection is complete, rather than right after loading installed plugins. This
@@ -5509,7 +7884,7 @@ Bug Fixes
   Thanks :user:`adborden` for the report and :user:`nicoddemus` for the PR.
 
 * Clean up unittest TestCase objects after tests are complete (:issue:`1649`).
-  Thanks :user:`d_b_w` for the report and PR.
+  Thanks :user:`d-b-w` for the report and PR.
 
 
 3.0.3 (2016-09-28)
@@ -5524,7 +7899,7 @@ Bug Fixes
   Thanks :user:`nicoddemus` for the PR.
 
 * Fix pkg_resources import error in Jython projects (:issue:`1853`).
-  Thanks :user:`raquel-ucl` for the PR.
+  Thanks :user:`raquelalegre` for the PR.
 
 * Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception
   in Python 3 (:issue:`1944`).
@@ -5554,7 +7929,7 @@ Bug Fixes
   a sequence of strings) when modules are considered for assertion rewriting.
   Due to this bug, much more modules were being rewritten than necessary
   if a test suite uses ``pytest_plugins`` to load internal plugins (:issue:`1888`).
-  Thanks :user:`jaraco` for the report and :user:`nicoddemus` for the PR (:pull:`1891`).
+  Thanks :user:`jaraco` for the report and :user:`nicoddemus` for the PR (:pr:`1891`).
 
 * Do not call tearDown and cleanups when running tests from
   ``unittest.TestCase`` subclasses with ``--pdb``
@@ -5609,12 +7984,12 @@ time or change existing behaviors in order to make them less surprising/more use
   * ``--nomagic``: use ``--assert=plain`` instead;
   * ``--report``: use ``-r`` instead;
 
-  Thanks to :user:`RedBeardCode` for the PR (:pull:`1664`).
+  Thanks to :user:`RedBeardCode` for the PR (:pr:`1664`).
 
 * ImportErrors in plugins now are a fatal error instead of issuing a
   pytest warning (:issue:`1479`). Thanks to :user:`The-Compiler` for the PR.
 
-* Removed support code for Python 3 versions < 3.3 (:pull:`1627`).
+* Removed support code for Python 3 versions < 3.3 (:pr:`1627`).
 
 * Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points
   were never documented and a leftover from a pre-virtualenv era. These entry
@@ -5625,19 +8000,19 @@ time or change existing behaviors in order to make them less surprising/more use
 * ``pytest.skip()`` now raises an error when used to decorate a test function,
   as opposed to its original intent (to imperatively skip a test inside a test function). Previously
   this usage would cause the entire module to be skipped (:issue:`607`).
-  Thanks :user:`omarkohl` for the complete PR (:pull:`1519`).
+  Thanks :user:`omarkohl` for the complete PR (:pr:`1519`).
 
 * Exit tests if a collection error occurs. A poll indicated most users will hit CTRL-C
   anyway as soon as they see collection errors, so pytest might as well make that the default behavior (:issue:`1421`).
   A ``--continue-on-collection-errors`` option has been added to restore the previous behaviour.
-  Thanks :user:`olegpidsadnyi` and :user:`omarkohl` for the complete PR (:pull:`1628`).
+  Thanks :user:`olegpidsadnyi` and :user:`omarkohl` for the complete PR (:pr:`1628`).
 
 * Renamed the pytest ``pdb`` module (plugin) into ``debugging`` to avoid clashes with the builtin ``pdb`` module.
 
 * Raise a helpful failure message when requesting a parametrized fixture at runtime,
   e.g. with ``request.getfixturevalue``. Previously these parameters were simply
   never defined, so a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])``
-  only ran once (:pull:`460`).
+  only ran once (:pr:`460`).
   Thanks to :user:`nikratio` for the bug report, :user:`RedBeardCode` and :user:`tomviner` for the PR.
 
 * ``_pytest.monkeypatch.monkeypatch`` class has been renamed to ``_pytest.monkeypatch.MonkeyPatch``
@@ -5655,7 +8030,7 @@ time or change existing behaviors in order to make them less surprising/more use
 
 * New ``doctest_namespace`` fixture for injecting names into the
   namespace in which doctests run.
-  Thanks :user:`milliams` for the complete PR (:pull:`1428`).
+  Thanks :user:`milliams` for the complete PR (:pr:`1428`).
 
 * New ``--doctest-report`` option available to change the output format of diffs
   when running (failing) doctests (implements :issue:`1749`).
@@ -5663,23 +8038,23 @@ time or change existing behaviors in order to make them less surprising/more use
 
 * New ``name`` argument to ``pytest.fixture`` decorator which allows a custom name
   for a fixture (to solve the funcarg-shadowing-fixture problem).
-  Thanks :user:`novas0x2a` for the complete PR (:pull:`1444`).
+  Thanks :user:`novas0x2a` for the complete PR (:pr:`1444`).
 
 * New ``approx()`` function for easily comparing floating-point numbers in
   tests.
-  Thanks :user:`kalekundert` for the complete PR (:pull:`1441`).
+  Thanks :user:`kalekundert` for the complete PR (:pr:`1441`).
 
 * Ability to add global properties in the final xunit output file by accessing
   the internal ``junitxml`` plugin (experimental).
-  Thanks :user:`tareqalayan` for the complete PR :pull:`1454`).
+  Thanks :user:`tareqalayan` for the complete PR :pr:`1454`).
 
 * New ``ExceptionInfo.match()`` method to match a regular expression on the
   string representation of an exception (:issue:`372`).
-  Thanks :user:`omarkohl` for the complete PR (:pull:`1502`).
+  Thanks :user:`omarkohl` for the complete PR (:pr:`1502`).
 
 * ``__tracebackhide__`` can now also be set to a callable which then can decide
   whether to filter the traceback based on the ``ExceptionInfo`` object passed
-  to it. Thanks :user:`The-Compiler` for the complete PR (:pull:`1526`).
+  to it. Thanks :user:`The-Compiler` for the complete PR (:pr:`1526`).
 
 * New ``pytest_make_parametrize_id(config, val)`` hook which can be used by plugins to provide
   friendly strings for custom types.
@@ -5697,7 +8072,7 @@ time or change existing behaviors in order to make them less surprising/more use
 * Introduce ``pytest`` command as recommended entry point. Note that ``py.test``
   still works and is not scheduled for removal. Closes proposal
   :issue:`1629`. Thanks :user:`obestwalter` and :user:`davehunt` for the complete PR
-  (:pull:`1633`).
+  (:pr:`1633`).
 
 * New cli flags:
 
@@ -5741,19 +8116,19 @@ time or change existing behaviors in order to make them less surprising/more use
 
 * Change ``report.outcome`` for ``xpassed`` tests to ``"passed"`` in non-strict
   mode and ``"failed"`` in strict mode. Thanks to :user:`hackebrot` for the PR
-  (:pull:`1795`) and :user:`gprasad84` for report (:issue:`1546`).
+  (:pr:`1795`) and :user:`gprasad84` for report (:issue:`1546`).
 
 * Tests marked with ``xfail(strict=False)`` (the default) now appear in
   JUnitXML reports as passing tests instead of skipped.
-  Thanks to :user:`hackebrot` for the PR (:pull:`1795`).
+  Thanks to :user:`hackebrot` for the PR (:pr:`1795`).
 
 * Highlight path of the file location in the error report to make it easier to copy/paste.
-  Thanks :user:`suzaku` for the PR (:pull:`1778`).
+  Thanks :user:`suzaku` for the PR (:pr:`1778`).
 
 * Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like
   those marked with the ``@pytest.yield_fixture`` decorator. This change renders
   ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements
-  the preferred way to write teardown code (:pull:`1461`).
+  the preferred way to write teardown code (:pr:`1461`).
   Thanks :user:`csaftoiu` for bringing this to attention and :user:`nicoddemus` for the PR.
 
 * Explicitly passed parametrize ids do not get escaped to ascii (:issue:`1351`).
@@ -5764,11 +8139,11 @@ time or change existing behaviors in order to make them less surprising/more use
   Thanks :user:`nicoddemus` for the PR.
 
 * ``pytest_terminal_summary`` hook now receives the ``exitstatus``
-  of the test session as argument. Thanks :user:`blueyed` for the PR (:pull:`1809`).
+  of the test session as argument. Thanks :user:`blueyed` for the PR (:pr:`1809`).
 
 * Parametrize ids can accept ``None`` as specific test id, in which case the
   automatically generated id for that argument will be used.
-  Thanks :user:`palaviv` for the complete PR (:pull:`1468`).
+  Thanks :user:`palaviv` for the complete PR (:pr:`1468`).
 
 * The parameter to xunit-style setup/teardown methods (``setup_method``,
   ``setup_module``, etc.) is now optional and may be omitted.
@@ -5776,32 +8151,32 @@ time or change existing behaviors in order to make them less surprising/more use
 
 * Improved automatic id generation selection in case of duplicate ids in
   parametrize.
-  Thanks :user:`palaviv` for the complete PR (:pull:`1474`).
+  Thanks :user:`palaviv` for the complete PR (:pr:`1474`).
 
 * Now pytest warnings summary is shown up by default. Added a new flag
   ``--disable-pytest-warnings`` to explicitly disable the warnings summary (:issue:`1668`).
 
 * Make ImportError during collection more explicit by reminding
   the user to check the name of the test module/package(s) (:issue:`1426`).
-  Thanks :user:`omarkohl` for the complete PR (:pull:`1520`).
+  Thanks :user:`omarkohl` for the complete PR (:pr:`1520`).
 
 * Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks
   :user:`mikofski` for the report and :user:`tomviner` for the PR (:issue:`1544`).
 
 * ``pytest.raises`` in the context manager form accepts a custom
   ``message`` to raise when no exception occurred.
-  Thanks :user:`palaviv` for the complete PR (:pull:`1616`).
+  Thanks :user:`palaviv` for the complete PR (:pr:`1616`).
 
 * ``conftest.py`` files now benefit from assertion rewriting; previously it
   was only available for test modules. Thanks :user:`flub`, :user:`sober7` and
   :user:`nicoddemus` for the PR (:issue:`1619`).
 
 * Text documents without any doctests no longer appear as "skipped".
-  Thanks :user:`graingert` for reporting and providing a full PR (:pull:`1580`).
+  Thanks :user:`graingert` for reporting and providing a full PR (:pr:`1580`).
 
 * Ensure that a module within a namespace package can be found when it
   is specified on the command line together with the ``--pyargs``
-  option.  Thanks to :user:`taschini` for the PR (:pull:`1597`).
+  option.  Thanks to :user:`taschini` for the PR (:pr:`1597`).
 
 * Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding
   sub-expressions that happened to be ``False``, assuming this was redundant information.
@@ -5817,20 +8192,20 @@ time or change existing behaviors in order to make them less surprising/more use
   Thanks :user:`nicoddemus` for the PR.
 
 * ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
-  to avoid conflicts with other distutils commands (see :pull:`567`). ``[pytest]`` sections in
+  to avoid conflicts with other distutils commands (see :pr:`567`). ``[pytest]`` sections in
   ``pytest.ini`` or ``tox.ini`` files are supported and unchanged.
   Thanks :user:`nicoddemus` for the PR.
 
 * Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be
-  removed in pytest-4.0 (:pull:`1684`).
+  removed in pytest-4.0 (:pr:`1684`).
   Thanks :user:`nicoddemus` for the PR.
 
 * Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled
-  for removal in pytest-4.0. It is recommended to pass a list of arguments instead (:pull:`1723`).
+  for removal in pytest-4.0. It is recommended to pass a list of arguments instead (:pr:`1723`).
 
 * Rename ``getfuncargvalue`` to ``getfixturevalue``. ``getfuncargvalue`` is
   still present but is now considered deprecated. Thanks to :user:`RedBeardCode` and :user:`tomviner`
-  for the PR (:pull:`1626`).
+  for the PR (:pr:`1626`).
 
 * ``optparse`` type usage now triggers DeprecationWarnings (:issue:`1740`).
 
@@ -5888,11 +8263,11 @@ time or change existing behaviors in order to make them less surprising/more use
   :user:`tomviner` for the PR.
 
 * ``ConftestImportFailure`` now shows the traceback making it easier to
-  identify bugs in ``conftest.py`` files (:pull:`1516`). Thanks :user:`txomon` for
+  identify bugs in ``conftest.py`` files (:pr:`1516`). Thanks :user:`txomon` for
   the PR.
 
 * Text documents without any doctests no longer appear as "skipped".
-  Thanks :user:`graingert` for reporting and providing a full PR (:pull:`1580`).
+  Thanks :user:`graingert` for reporting and providing a full PR (:pr:`1580`).
 
 * Fixed collection of classes with custom ``__new__`` method.
   Fixes :issue:`1579`. Thanks to :user:`Stranger6667` for the PR.
@@ -5900,7 +8275,7 @@ time or change existing behaviors in order to make them less surprising/more use
 * Fixed scope overriding inside metafunc.parametrize (:issue:`634`).
   Thanks to :user:`Stranger6667` for the PR.
 
-* Fixed the total tests tally in junit xml output (:pull:`1798`).
+* Fixed the total tests tally in junit xml output (:pr:`1798`).
   Thanks to :user:`cboelsen` for the PR.
 
 * Fixed off-by-one error with lines from ``request.node.warn``.
@@ -5917,14 +8292,14 @@ time or change existing behaviors in order to make them less surprising/more use
 
 * Fix Xfail does not work with condition keyword argument.
   Thanks :user:`astraw38` for reporting the issue (:issue:`1496`) and :user:`tomviner`
-  for PR the (:pull:`1524`).
+  for PR the (:pr:`1524`).
 
 * Fix win32 path issue when putting custom config file with absolute path
   in ``pytest.main("-c your_absolute_path")``.
 
 * Fix maximum recursion depth detection when raised error class is not aware
   of unicode/encoded bytes.
-  Thanks :user:`prusse-martin` for the PR (:pull:`1506`).
+  Thanks :user:`prusse-martin` for the PR (:pr:`1506`).
 
 * Fix ``pytest.mark.skip`` mark when used in strict mode.
   Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for
@@ -5951,7 +8326,7 @@ time or change existing behaviors in order to make them less surprising/more use
   Thanks :user:`nicoddemus` for the PR.
 
 * Fix (:issue:`469`): junit parses report.nodeid incorrectly, when params IDs
-  contain ``::``. Thanks :user:`tomviner` for the PR (:pull:`1431`).
+  contain ``::``. Thanks :user:`tomviner` for the PR (:pr:`1431`).
 
 * Fix (:issue:`578`): SyntaxErrors
   containing non-ascii lines at the point of failure generated an internal
@@ -5972,7 +8347,7 @@ time or change existing behaviors in order to make them less surprising/more use
 **New Features**
 
 * New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
-  Thanks :user:`MichaelAquilina` for the complete PR (:pull:`1040`).
+  Thanks :user:`MichaelAquilina` for the complete PR (:pr:`1040`).
 
 * ``--doctest-glob`` may now be passed multiple times in the command-line.
   Thanks :user:`jab` and :user:`nicoddemus` for the PR.
@@ -5983,14 +8358,14 @@ time or change existing behaviors in order to make them less surprising/more use
 * ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS``
   tests to fail the test suite (defaulting to ``False``). There's also a
   ``xfail_strict`` ini option that can be used to configure it project-wise.
-  Thanks :user:`rabbbit` for the request and :user:`nicoddemus` for the PR (:pull:`1355`).
+  Thanks :user:`rabbbit` for the request and :user:`nicoddemus` for the PR (:pr:`1355`).
 
 * ``Parser.addini`` now supports options of type ``bool``.
   Thanks :user:`nicoddemus` for the PR.
 
 * New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings
   in doctest output (similar to ``ALLOW_UNICODE``).
-  Thanks :user:`jaraco` for the request and :user:`nicoddemus` for the PR (:pull:`1287`).
+  Thanks :user:`jaraco` for the request and :user:`nicoddemus` for the PR (:pr:`1287`).
 
 * Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors.
   Fixes :issue:`1366`.
@@ -6022,7 +8397,7 @@ time or change existing behaviors in order to make them less surprising/more use
 
 * Removed code and documentation for Python 2.5 or lower versions,
   including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
-  Thanks :user:`nicoddemus` for the PR (:pull:`1226`).
+  Thanks :user:`nicoddemus` for the PR (:pr:`1226`).
 
 * Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
   found in the environment, even when ``-vv`` isn't used.
@@ -6389,7 +8764,7 @@ time or change existing behaviors in order to make them less surprising/more use
   one will also have a "reprec" attribute with the recorded events/reports.
 
 - fix monkeypatch.setattr("x.y", raising=False) to actually not raise
-  if "y" is not a pre-existing attribute. Thanks Florian Bruhin.
+  if "y" is not a preexisting attribute. Thanks Florian Bruhin.
 
 - fix issue741: make running output from testdir.run copy/pasteable
   Thanks Bruno Oliveira.
@@ -6445,7 +8820,7 @@ time or change existing behaviors in order to make them less surprising/more use
 
 - fix issue854: autouse yield_fixtures defined as class members of
   unittest.TestCase subclasses now work as expected.
-  Thannks xmo-odoo for the report and Bruno Oliveira for the PR.
+  Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
 
 - fix issue833: --fixtures now shows all fixtures of collected test files, instead of just the
   fixtures declared on the first one.
@@ -6549,7 +8924,7 @@ time or change existing behaviors in order to make them less surprising/more use
   github.  See https://pytest.org/en/stable/contributing.html .
   Thanks to Anatoly for pushing and initial work on this.
 
-- fix issue650: new option ``--docttest-ignore-import-errors`` which
+- fix issue650: new option ``--doctest-ignore-import-errors`` which
   will turn import errors in doctests into skips.  Thanks Charles Cloud
   for the complete PR.
 
@@ -6737,7 +9112,7 @@ time or change existing behaviors in order to make them less surprising/more use
 - cleanup setup.py a bit and specify supported versions. Thanks Jurko
   Gospodnetic for the PR.
 
-- change XPASS colour to yellow rather then red when tests are run
+- change XPASS colour to yellow rather than red when tests are run
   with -v.
 
 - fix issue473: work around mock putting an unbound method into a class
@@ -6910,7 +9285,7 @@ time or change existing behaviors in order to make them less surprising/more use
   Thanks Ralph Schmitt for the precise failure example.
 
 - fix issue244 by implementing special index for parameters to only use
-  indices for paramentrized test ids
+  indices for parametrized test ids
 
 - fix issue287 by running all finalizers but saving the exception
   from the first failing finalizer and re-raising it so teardown will
@@ -6918,7 +9293,7 @@ time or change existing behaviors in order to make them less surprising/more use
   it might be the cause for other finalizers to fail.
 
 - fix ordering when mock.patch or other standard decorator-wrappings
-  are used with test methods.  This fixues issue346 and should
+  are used with test methods.  This fixes issue346 and should
   help with random "xdist" collection failures.  Thanks to
   Ronny Pfannschmidt and Donald Stufft for helping to isolate it.
 
@@ -7175,7 +9550,7 @@ Bug fixes:
   partially failed (finalizers would not always be called before)
 
 - fix issue320 - fix class scope for fixtures when mixed with
-  module-level functions.  Thanks Anatloy Bubenkoff.
+  module-level functions.  Thanks Anatoly Bubenkoff.
 
 - you can specify "-q" or "-qq" to get different levels of "quieter"
   reporting (thanks Katarzyna Jachim)
@@ -7533,7 +9908,7 @@ Bug fixes:
   or through plugin hooks.  Also introduce a "--strict" option which
   will treat unregistered markers as errors
   allowing to avoid typos and maintain a well described set of markers
-  for your test suite.  See exaples at http://pytest.org/en/stable/how-to/mark.html
+  for your test suite.  See examples at http://pytest.org/en/stable/how-to/mark.html
   and its links.
 - issue50: introduce "-m marker" option to select tests based on markers
   (this is a stricter and more predictable version of '-k' in that "-m"
@@ -7597,7 +9972,7 @@ Bug fixes:
   unexpected exceptions
 - fix issue47: timing output in junitxml for test cases is now correct
 - fix issue48: typo in MarkInfo repr leading to exception
-- fix issue49: avoid confusing error when initizaliation partially fails
+- fix issue49: avoid confusing error when initialization partially fails
 - fix issue44: env/username expansion for junitxml file path
 - show releaselevel information in test runs for pypy
 - reworked doc pages for better navigation and PDF generation
@@ -7722,7 +10097,7 @@ Bug fixes:
   collection-before-running semantics were not
   setup as with pytest 1.3.4.  Note, however, that
   the recommended and much cleaner way to do test
-  parametraization remains the "pytest_generate_tests"
+  parameterization remains the "pytest_generate_tests"
   mechanism, see the docs.
 
 2.0.0 (2010-11-25)
@@ -7845,7 +10220,7 @@ Bug fixes:
 - fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
 - fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
 - fix py.code.compile(source) to generate unique filenames
-- fix assertion re-interp problems on PyPy, by defering code
+- fix assertion re-interp problems on PyPy, by deferring code
   compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
 - fix py.path.local.pyimport() to work with directories
 - streamline py.path.local.mkdtemp implementation and usage
@@ -7919,7 +10294,7 @@ Bug fixes:
 - improve support for raises and other dynamically compiled code by
   manipulating python's linecache.cache instead of the previous
   rather hacky way of creating custom code objects.  This makes
-  it seemlessly work on Jython and PyPy where it previously didn't.
+  it seamlessly work on Jython and PyPy where it previously didn't.
 
 - fix issue96: make capturing more resilient against Control-C
   interruptions (involved somewhat substantial refactoring
diff --git a/doc/en/conf.py b/doc/en/conf.py
index bcf01aa4713..c89e14d07fa 100644
--- a/doc/en/conf.py
+++ b/doc/en/conf.py
@@ -1,80 +1,42 @@
-#
-# pytest documentation build configuration file, created by
-# sphinx-quickstart on Fri Oct  8 17:54:28 2010.
-#
-# This file is execfile()d with the current directory set to its containing dir.
-#
-# Note that not all possible configuration values are present in this
-# autogenerated file.
-#
-# All configuration values have a default; values that are commented out
-# serve to show the default.
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# The full version, including alpha/beta/rc tags.
-# The short X.Y version.
-import ast
+from __future__ import annotations
+
 import os
+from pathlib import Path
 import shutil
-import sys
 from textwrap import dedent
-from typing import List
 from typing import TYPE_CHECKING
 
-from _pytest import __version__ as version
+from pytest import __version__ as full_version
+
 
 if TYPE_CHECKING:
     import sphinx.application
 
+PROJECT_ROOT_DIR = Path(__file__).parents[2].resolve()
 
-release = ".".join(version.split(".")[:2])
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-# sys.path.insert(0, os.path.abspath('.'))
-
-autodoc_member_order = "bysource"
-autodoc_typehints = "description"
-todo_include_todos = 1
-
-latex_engine = "lualatex"
-
-latex_elements = {
-    "preamble": dedent(
-        r"""
-        \directlua{
-            luaotfload.add_fallback("fallbacks", {
-                "Noto Serif CJK SC:style=Regular;",
-                "Symbola:Style=Regular;"
-            })
-        }
-
-        \setmainfont{FreeSerif}[RawFeature={fallback=fallbacks}]
-        """
-    )
-}
+# -- Project information ---------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
 
-# -- General configuration -----------------------------------------------------
+project = "pytest"
+copyright = "2015, holger krekel and pytest-dev team"
+version = full_version.split("+")[0]
+release = ".".join(version.split(".")[:2])
 
-# If your documentation needs a minimal Sphinx version, state it here.
-# needs_sphinx = '1.0'
+# -- General configuration -------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
 
-# Add any Sphinx extension module names here, as strings. They can be extensions
-# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+root_doc = "index"
 extensions = [
-    "pallets_sphinx_themes",
     "pygments_pytest",
     "sphinx.ext.autodoc",
     "sphinx.ext.autosummary",
-    "sphinx.ext.extlinks",
     "sphinx.ext.intersphinx",
     "sphinx.ext.todo",
     "sphinx.ext.viewcode",
     "sphinx_removed_in",
     "sphinxcontrib_trio",
+    "sphinxcontrib.towncrier.ext",  # provides `towncrier-draft-entries` directive
+    "sphinx_issues",  # implements `:issue:`, `:pr:` and other GH-related roles
 ]
 
 # Building PDF docs on readthedocs requires inkscape for svg to pdf
@@ -84,35 +46,6 @@
 if shutil.which("inkscape"):
     extensions.append("sphinxcontrib.inkscapeconverter")
 
-# Add any paths that contain templates here, relative to this directory.
-templates_path = ["_templates"]
-
-# The suffix of source filenames.
-source_suffix = ".rst"
-
-# The encoding of source files.
-# source_encoding = 'utf-8-sig'
-
-# The master toctree document.
-master_doc = "contents"
-
-# General information about the project.
-project = "pytest"
-copyright = "2015, holger krekel and pytest-dev team"
-
-
-# The language for content autogenerated by Sphinx. Refer to documentation
-# for a list of supported languages.
-# language = None
-
-# There are two options for replacing |today|: either, you set today to some
-# non-false value, then it is used:
-# today = ''
-# Else, today_fmt is used as the format for a strftime call.
-# today_fmt = '%B %d, %Y'
-
-# List of patterns, relative to source directory, that match files and
-# directories to ignore when looking for source files.
 exclude_patterns = [
     "_build",
     "naming20.rst",
@@ -124,165 +57,144 @@
     "setup.rst",
     "example/remoteinterp.rst",
 ]
-
-
-# The reST default role (used for this markup: `text`) to use for all documents.
+templates_path = ["_templates"]
 default_role = "literal"
 
-# If true, '()' will be appended to :func: etc. cross-reference text.
-# add_function_parentheses = True
+nitpicky = True
+nitpick_ignore = [
+    # TODO (fix in pluggy?)
+    ("py:class", "HookCaller"),
+    ("py:class", "HookspecMarker"),
+    ("py:exc", "PluginValidationError"),
+    # Might want to expose/TODO (https://github.com/pytest-dev/pytest/issues/7469)
+    ("py:class", "ExceptionRepr"),
+    ("py:class", "Exit"),
+    ("py:class", "SubRequest"),
+    ("py:class", "SubRequest"),
+    ("py:class", "TerminalReporter"),
+    ("py:class", "_pytest._code.code.TerminalRepr"),
+    ("py:class", "TerminalRepr"),
+    ("py:class", "_pytest.fixtures.FixtureFunctionMarker"),
+    ("py:class", "_pytest.fixtures.FixtureFunctionDefinition"),
+    ("py:class", "_pytest.logging.LogCaptureHandler"),
+    ("py:class", "_pytest.mark.structures.ParameterSet"),
+    # Intentionally undocumented/private
+    ("py:class", "_pytest._code.code.Traceback"),
+    ("py:class", "_pytest._py.path.LocalPath"),
+    ("py:class", "_pytest.capture.CaptureResult"),
+    ("py:class", "_pytest.compat.NotSetType"),
+    ("py:class", "_pytest.python.PyCollector"),
+    ("py:class", "_pytest.python.PyobjMixin"),
+    ("py:class", "_pytest.python_api.RaisesContext"),
+    ("py:class", "_pytest.recwarn.WarningsChecker"),
+    ("py:class", "_pytest.reports.BaseReport"),
+    # Undocumented third parties
+    ("py:class", "_tracing.TagTracerSub"),
+    ("py:class", "warnings.WarningMessage"),
+    # Undocumented type aliases
+    ("py:class", "LEGACY_PATH"),
+    ("py:class", "_PluggyPlugin"),
+    # TypeVars
+    ("py:class", "_pytest._code.code.E"),
+    ("py:class", "E"),  # due to delayed annotation
+    ("py:class", "_pytest.fixtures.FixtureFunction"),
+    ("py:class", "_pytest.nodes._NodeType"),
+    ("py:class", "_NodeType"),  # due to delayed annotation
+    ("py:class", "_pytest.python_api.E"),
+    ("py:class", "_pytest.recwarn.T"),
+    ("py:class", "_pytest.runner.TResult"),
+    ("py:obj", "_pytest.fixtures.FixtureValue"),
+    ("py:obj", "_pytest.stash.T"),
+    ("py:class", "_ScopeName"),
+    ("py:class", "BaseExcT_1"),
+    ("py:class", "ExcT_1"),
+]
 
-# If true, the current module name will be prepended to all description
-# unit titles (such as .. function::).
 add_module_names = False
 
-# If true, sectionauthor and moduleauthor directives will be shown in the
-# output. They are ignored by default.
-# show_authors = False
+# -- Options for Autodoc --------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration
 
-# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = "sphinx"
+autodoc_member_order = "bysource"
+autodoc_typehints = "description"
+autodoc_typehints_description_target = "documented"
 
+# -- Options for intersphinx ----------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration
 
-# A list of ignored prefixes for module index sorting.
-# modindex_common_prefix = []
+intersphinx_mapping = {
+    "pluggy": ("https://pluggy.readthedocs.io/en/stable", None),
+    "python": ("https://docs.python.org/3", None),
+    "numpy": ("https://numpy.org/doc/stable", None),
+    "pip": ("https://pip.pypa.io/en/stable", None),
+    "tox": ("https://tox.wiki/en/stable", None),
+    "virtualenv": ("https://virtualenv.pypa.io/en/stable", None),
+    "setuptools": ("https://setuptools.pypa.io/en/stable", None),
+    "packaging": ("https://packaging.python.org/en/latest", None),
+}
+
+# -- Options for todo -----------------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/extensions/todo.html#configuration
+
+todo_include_todos = True
+
+# -- Options for linkcheck builder ----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-the-linkcheck-builder
 
-# A list of regular expressions that match URIs that should not be checked when
-# doing a linkcheck.
 linkcheck_ignore = [
     "https://blogs.msdn.microsoft.com/bharry/2017/06/28/testing-in-a-cloud-delivery-cadence/",
     "http://pythontesting.net/framework/pytest-introduction/",
     r"https://github.com/pytest-dev/pytest/issues/\d+",
     r"https://github.com/pytest-dev/pytest/pull/\d+",
 ]
-
-# The number of worker threads to use when checking links (default=5).
 linkcheck_workers = 5
 
+# -- Options for HTML output ----------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
 
-_repo = "https://github.com/pytest-dev/pytest"
-extlinks = {
-    "bpo": ("https://bugs.python.org/issue%s", "bpo-"),
-    "pypi": ("https://pypi.org/project/%s/", ""),
-    "issue": (f"{_repo}/issues/%s", "issue #"),
-    "pull": (f"{_repo}/pull/%s", "pull request #"),
-    "user": ("https://github.com/%s", "@"),
-}
-
-
-# -- Options for HTML output ---------------------------------------------------
-
-sys.path.append(os.path.abspath("_themes"))
-html_theme_path = ["_themes"]
-
-# The theme to use for HTML and HTML Help pages.  See the documentation for
-# a list of builtin themes.
-html_theme = "flask"
-
-# Theme options are theme-specific and customize the look and feel of a theme
-# further.  For a list of options available for each theme, see the
-# documentation.
-# html_theme_options = {"index_logo": None}
+html_theme = "furo"
+html_theme_options = {"sidebar_hide_name": True}
 
-# Add any paths that contain custom themes here, relative to this directory.
-# html_theme_path = []
+html_static_path = ["_static"]
+html_css_files = [
+    "pytest-custom.css",
+]
 
-# The name for this set of Sphinx documents.  If None, it defaults to
-# "<project> v<release> documentation".
 html_title = "pytest documentation"
+html_short_title = f"pytest-{release}"
 
-# A shorter title for the navigation bar.  Default is the same as html_title.
-html_short_title = "pytest-%s" % release
-
-# The name of an image file (relative to this directory) to place at the top
-# of the sidebar.
-html_logo = "img/pytest_logo_curves.svg"
-
-# The name of an image file (within the static path) to use as favicon of the
-# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
-# pixels large.
+html_logo = "_static/pytest1.png"
 html_favicon = "img/favicon.png"
 
-# Add any paths that contain custom static files (such as style sheets) here,
-# relative to this directory. They are copied after the builtin static files,
-# so a file named "default.css" will overwrite the builtin "default.css".
-# html_static_path = ['_static']
-
-# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
-# using the given strftime format.
-# html_last_updated_fmt = '%b %d, %Y'
-
-# If true, SmartyPants will be used to convert quotes and dashes to
-# typographically correct entities.
-# html_use_smartypants = True
-
-# Custom sidebar templates, maps document names to template names.
-# html_sidebars = {}
-# html_sidebars = {'index': 'indexsidebar.html'}
-
-html_sidebars = {
-    "index": [
-        "slim_searchbox.html",
-        "sidebarintro.html",
-        "globaltoc.html",
-        "links.html",
-        "sourcelink.html",
-    ],
-    "**": [
-        "slim_searchbox.html",
-        "globaltoc.html",
-        "relations.html",
-        "links.html",
-        "sourcelink.html",
-    ],
-}
-
-# Additional templates that should be rendered to pages, maps page names to
-# template names.
-# html_additional_pages = {}
-# html_additional_pages = {'index': 'index.html'}
-
-
-# If false, no module index is generated.
-html_domain_indices = True
-
-# If false, no index is generated.
-html_use_index = True
-
-# If true, the index is split into individual pages for each letter.
-# html_split_index = False
-
-# If true, links to the reST sources are added to the pages.
+html_use_index = False
 html_show_sourcelink = False
 
-# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
-# html_show_sphinx = True
+html_baseurl = "https://docs.pytest.org/en/stable/"
 
-# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
-# html_show_copyright = True
+# -- Options for HTML Help output -----------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-help-output
 
-# If true, an OpenSearch description file will be output, and all pages will
-# contain a <link> tag referring to it.  The value of this option must be the
-# base URL from which the finished HTML is served.
-# html_use_opensearch = ''
+htmlhelp_basename = "pytestdoc"
 
-# This is the file name suffix for HTML files (e.g. ".xhtml").
-# html_file_suffix = None
 
-# Output file base name for HTML help builder.
-htmlhelp_basename = "pytestdoc"
+# -- Options for manual page output ---------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-manual-page-output
 
+man_pages = [
+    ("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)
+]
 
-# -- Options for LaTeX output --------------------------------------------------
+# -- Options for epub output ----------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-epub-output
 
-# The paper size ('letter' or 'a4').
-# latex_paper_size = 'letter'
+epub_title = "pytest"
+epub_author = "holger krekel at merlinux eu"
+epub_publisher = "holger krekel at merlinux eu"
+epub_copyright = "2013, holger krekel et alii"
 
-# The font size ('10pt', '11pt' or '12pt').
-# latex_font_size = '10pt'
+# -- Options for LaTeX output --------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-latex-output
 
-# Grouping the document tree into LaTeX files. List of tuples
-# (source start file, target name, title, author, documentclass [howto/manual]).
 latex_documents = [
     (
         "contents",
@@ -292,82 +204,29 @@
         "manual",
     )
 ]
-
-# The name of an image file (relative to this directory) to place at the top of
-# the title page.
-latex_logo = "img/pytest1.png"
-
-# For "manual" documents, if this is true, then toplevel headings are parts,
-# not chapters.
-# latex_use_parts = False
-
-# If true, show page references after internal links.
-# latex_show_pagerefs = False
-
-# If true, show URL addresses after external links.
-# latex_show_urls = False
-
-# Additional stuff for the LaTeX preamble.
-# latex_preamble = ''
-
-# Documents to append as an appendix to all manuals.
-# latex_appendices = []
-
-# If false, no module index is generated.
 latex_domain_indices = False
+latex_engine = "lualatex"
+latex_elements = {
+    "preamble": dedent(
+        r"""
+        \directlua{
+            luaotfload.add_fallback("fallbacks", {
+                "Noto Serif CJK SC:style=Regular;",
+                "Symbola:Style=Regular;"
+            })
+        }
 
-# -- Options for manual page output --------------------------------------------
-
-# One entry per manual page. List of tuples
-# (source start file, name, description, authors, manual section).
-man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)]
-
-
-# -- Options for Epub output ---------------------------------------------------
-
-# Bibliographic Dublin Core info.
-epub_title = "pytest"
-epub_author = "holger krekel at merlinux eu"
-epub_publisher = "holger krekel at merlinux eu"
-epub_copyright = "2013, holger krekel et alii"
-
-# The language of the text. It defaults to the language option
-# or en if the language is not set.
-# epub_language = ''
-
-# The scheme of the identifier. Typical schemes are ISBN or URL.
-# epub_scheme = ''
-
-# The unique identifier of the text. This can be a ISBN number
-# or the project homepage.
-# epub_identifier = ''
-
-# A unique identification for the text.
-# epub_uid = ''
-
-# HTML files that should be inserted before the pages created by sphinx.
-# The format is a list of tuples containing the path and title.
-# epub_pre_files = []
-
-# HTML files shat should be inserted after the pages created by sphinx.
-# The format is a list of tuples containing the path and title.
-# epub_post_files = []
-
-# A list of files that should not be packed into the epub file.
-# epub_exclude_files = []
-
-# The depth of the table of contents in toc.ncx.
-# epub_tocdepth = 3
-
-# Allow duplicate toc entries.
-# epub_tocdup = True
-
+        \setmainfont{FreeSerif}[RawFeature={fallback=fallbacks}]
+        """
+    )
+}
 
-# -- Options for texinfo output ------------------------------------------------
+# -- Options for texinfo output -------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-texinfo-output
 
 texinfo_documents = [
     (
-        master_doc,
+        root_doc,
         "pytest",
         "pytest Documentation",
         (
@@ -381,49 +240,37 @@
     )
 ]
 
+# -- Options for towncrier_draft extension --------------------------------------------
+# https://sphinxcontrib-towncrier.readthedocs.io/en/latest/#how-to-use-this
 
-# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {
-    "pluggy": ("https://pluggy.readthedocs.io/en/stable", None),
-    "python": ("https://docs.python.org/3", None),
-    "numpy": ("https://numpy.org/doc/stable", None),
-    "pip": ("https://pip.pypa.io/en/stable", None),
-    "tox": ("https://tox.wiki/en/stable", None),
-    "virtualenv": ("https://virtualenv.pypa.io/en/stable", None),
-    "django": (
-        "http://docs.djangoproject.com/en/stable",
-        "http://docs.djangoproject.com/en/stable/_objects",
-    ),
-    "setuptools": ("https://setuptools.pypa.io/en/stable", None),
-}
+towncrier_draft_autoversion_mode = "draft"  # or: 'sphinx-version', 'sphinx-release'
+towncrier_draft_include_empty = True
+towncrier_draft_working_directory = PROJECT_ROOT_DIR
+towncrier_draft_config_path = "pyproject.toml"  # relative to cwd
 
+# -- Options for sphinx_issues extension -----------------------------------
+# https://github.com/sloria/sphinx-issues#installation-and-configuration
 
-def configure_logging(app: "sphinx.application.Sphinx") -> None:
-    """Configure Sphinx's WarningHandler to handle (expected) missing include."""
-    import sphinx.util.logging
-    import logging
+issues_github_path = "pytest-dev/pytest"
 
-    class WarnLogFilter(logging.Filter):
-        def filter(self, record: logging.LogRecord) -> bool:
-            """Ignore warnings about missing include with "only" directive.
+# -- Custom Read the Docs build configuration -----------------------------------------
+# https://docs.readthedocs.io/en/stable/reference/environment-variables.html#environment-variable-reference
+# https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#including-content-based-on-tags
 
-            Ref: https://github.com/sphinx-doc/sphinx/issues/2150."""
-            if (
-                record.msg.startswith('Problems with "include" directive path:')
-                and "_changelog_towncrier_draft.rst" in record.msg
-            ):
-                return False
-            return True
+IS_RELEASE_ON_RTD = (
+    os.getenv("READTHEDOCS", "False") == "True"
+    and os.environ["READTHEDOCS_VERSION_TYPE"] == "tag"
+)
+if IS_RELEASE_ON_RTD:
+    tags: set[str]
+    # pylint: disable-next=used-before-assignment
+    tags.add("is_release")  # noqa: F821
 
-    logger = logging.getLogger(sphinx.util.logging.NAMESPACE)
-    warn_handler = [x for x in logger.handlers if x.level == logging.WARNING]
-    assert len(warn_handler) == 1, warn_handler
-    warn_handler[0].filters.insert(0, WarnLogFilter())
+# -- Custom documentation plugin ------------------------------------------------------
+# https://www.sphinx-doc.org/en/master/development/tutorials/extending_syntax.html#the-setup-function
 
 
-def setup(app: "sphinx.application.Sphinx") -> None:
-    # from sphinx.ext.autodoc import cut_lines
-    # app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
+def setup(app: sphinx.application.Sphinx) -> None:
     app.add_crossref_type(
         "fixture",
         "fixture",
@@ -445,26 +292,12 @@ def setup(app: "sphinx.application.Sphinx") -> None:
         indextemplate="pair: %s; global variable interpreted by pytest",
     )
 
-    configure_logging(app)
-
-    # Make Sphinx mark classes with "final" when decorated with @final.
-    # We need this because we import final from pytest._compat, not from
-    # typing (for Python < 3.8 compat), so Sphinx doesn't detect it.
-    # To keep things simple we accept any `@final` decorator.
-    # Ref: https://github.com/pytest-dev/pytest/pull/7780
-    import sphinx.pycode.ast
-    import sphinx.pycode.parser
-
-    original_is_final = sphinx.pycode.parser.VariableCommentPicker.is_final
-
-    def patched_is_final(self, decorators: List[ast.expr]) -> bool:
-        if original_is_final(self, decorators):
-            return True
-        return any(
-            sphinx.pycode.ast.unparse(decorator) == "final" for decorator in decorators
-        )
-
-    sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final
+    app.add_crossref_type(
+        directivename="hook",
+        rolename="hook",
+        objname="pytest hook",
+        indextemplate="pair: %s; hook",
+    )
 
     # legacypath.py monkey-patches pytest.Testdir in. Import the file so
     # that autodoc can discover references to it.
diff --git a/doc/en/conftest.py b/doc/en/conftest.py
index 1a62e1b5df5..50e43a0b544 100644
--- a/doc/en/conftest.py
+++ b/doc/en/conftest.py
@@ -1 +1,4 @@
+from __future__ import annotations
+
+
 collect_ignore = ["conf.py"]
diff --git a/doc/en/contact.rst b/doc/en/contact.rst
index beed10d7f27..cd34f548e99 100644
--- a/doc/en/contact.rst
+++ b/doc/en/contact.rst
@@ -3,52 +3,59 @@
 .. _`contact`:
 
 Contact channels
-===================================
+================
 
-- `pytest issue tracker`_ to report bugs or suggest features (for version
-  2.0 and above).
-- `pytest discussions`_ at github for general questions.
-- `pytest discord server <https://discord.com/invite/pytest-dev>`_
-  for pytest development visibility and general assistance.
+Web
+---
+
+- `pytest issue tracker`_ to report bugs or suggest features.
+- `pytest discussions`_ at GitHub for general questions.
 - `pytest on stackoverflow.com <http://stackoverflow.com/search?q=pytest>`_
-  to post precise questions with the tag ``pytest``.  New Questions will usually
+  to post precise questions with the tag ``pytest``.  New questions will usually
   be seen by pytest users or developers and answered quickly.
 
-- `Testing In Python`_: a mailing list for Python testing tools and discussion.
-
-- `pytest-dev at python.org (mailing list)`_ pytest specific announcements and discussions.
-
-- :doc:`contribution guide <contributing>` for help on submitting pull
-  requests to GitHub.
+Chat
+----
 
+- `pytest discord server <https://discord.com/invite/pytest-dev>`_
+  for pytest development visibility and general assistance.
 - ``#pytest`` `on irc.libera.chat <ircs://irc.libera.chat:6697/#pytest>`_ IRC
-  channel for random questions (using an IRC client, `via webchat
-  <https://web.libera.chat/#pytest>`_, or `via Matrix
-  <https://matrix.to/#/%23pytest:libera.chat>`_).
+  channel for random questions (using an IRC client, or `via webchat
+  <https://web.libera.chat/#pytest>`_)
+- ``#pytest`` `on Matrix <https://matrix.to/#/#pytest:matrix.org>`_.
 
-- private mail to Holger.Krekel at gmail com if you want to communicate sensitive issues
+Microblogging
+-------------
 
+- Bluesky: `@pytest.org <https://bsky.app/profile/pytest.org>`_
+- Mastodon: `@pytest@fosstodon.org <https://fosstodon.org/@pytest>`_
+- Twitter/X: `@pytestdotorg <https://x.com/pytestdotorg>`_
 
-- `merlinux.eu`_ offers pytest and tox-related professional teaching and
-  consulting.
+Mail
+----
 
-.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
-.. _`old issue tracker`: https://bitbucket.org/hpk42/py-trunk/issues/
+- `Testing In Python`_: a mailing list for Python testing tools and discussion.
+- `pytest-dev at python.org`_ a mailing list for pytest specific announcements and discussions.
+- Mail to `core@pytest.org <mailto:core@pytest.org>`_ for topics that cannot be
+  discussed in public. Mails sent there will be distributed among the members
+  in the pytest core team, who can also be contacted individually:
+
+  * Bruno Oliveira (:user:`nicoddemus`, `bruno@pytest.org <mailto:bruno@pytest.org>`_)
+  * Florian Bruhin (:user:`The-Compiler`, `florian@pytest.org <mailto:florian@pytest.org>`_)
+  * Pierre Sassoulas (:user:`Pierre-Sassoulas`, `pierre@pytest.org <mailto:pierre@pytest.org>`_)
+  * Ran Benita (:user:`bluetech`, `ran@pytest.org <mailto:ran@pytest.org>`_)
+  * Ronny Pfannschmidt (:user:`RonnyPfannschmidt`, `ronny@pytest.org <mailto:ronny@pytest.org>`_)
+  * Zac Hatfield-Dodds (:user:`Zac-HD`, `zac@pytest.org <mailto:zac@pytest.org>`_)
+
+Other
+-----
+
+- The :doc:`contribution guide <contributing>` for help on submitting pull
+  requests to GitHub.
+- Florian Bruhin (:user:`The-Compiler`) offers pytest professional teaching and
+  consulting via `Bruhin Software <https://bruhin.software>`_.
 
+.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
 .. _`pytest discussions`: https://github.com/pytest-dev/pytest/discussions
-
-.. _`merlinux.eu`: https://merlinux.eu/
-
-.. _`get an account`:
-
-.. _tetamap: https://tetamap.wordpress.com/
-
-.. _`@pylibcommit`: https://twitter.com/pylibcommit
-
-
 .. _`Testing in Python`: http://lists.idyll.org/listinfo/testing-in-python
-.. _FOAF: https://en.wikipedia.org/wiki/FOAF
-.. _`py-dev`:
-.. _`development mailing list`:
-.. _`pytest-dev at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-dev
-.. _`pytest-commit at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-commit
+.. _`pytest-dev at python.org`: http://mail.python.org/mailman/listinfo/pytest-dev
diff --git a/doc/en/contents.rst b/doc/en/contents.rst
index 049d44ba9d8..07c0b3ff6b9 100644
--- a/doc/en/contents.rst
+++ b/doc/en/contents.rst
@@ -1,3 +1,5 @@
+:orphan:
+
 .. _toc:
 
 Full pytest documentation
@@ -44,7 +46,6 @@ How-to guides
 
    how-to/existingtestsuite
    how-to/unittest
-   how-to/nose
    how-to/xunit_setup
 
    how-to/bash-completion
@@ -85,7 +86,6 @@ Further topics
 
    backwards-compatibility
    deprecations
-   py27-py34-deprecation
 
    contributing
    development_guide
diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst
index ea9ef2aa35d..18df64c9204 100644
--- a/doc/en/deprecations.rst
+++ b/doc/en/deprecations.rst
@@ -7,34 +7,117 @@ This page lists all pytest features that are currently deprecated or have been r
 The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives
 should be used instead.
 
-.. contents::
-    :depth: 3
-    :local:
-
 
 Deprecated Features
 -------------------
 
 Below is a complete list of all pytest features which are considered deprecated. Using those features will issue
-:class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
+:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
 
-.. _instance-collector-deprecation:
 
-The ``pytest.Instance`` collector
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. _sync-test-async-fixture:
 
-.. versionremoved:: 7.0
+sync test depending on async fixture
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The ``pytest.Instance`` collector type has been removed.
+.. deprecated:: 8.4
 
-Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`.
-Now :class:`~pytest.Class` collects the test methods directly.
+Pytest has for a long time given an error when encountering an asynchronous test function, prompting the user to install
+a plugin that can handle it. It has not given any errors if you have an asynchronous fixture that's depended on by a
+synchronous test. If the fixture was an async function you did get an "unawaited coroutine" warning, but for async yield fixtures you didn't even get that.
+This is a problem even if you do have a plugin installed for handling async tests, as they may require
+special decorators for async fixtures to be handled, and some may not robustly handle if a user accidentally requests an
+async fixture from their sync tests. Fixture values being cached can make this even more unintuitive, where everything will
+"work" if the fixture is first requested by an async test, and then requested by a synchronous test.
 
-Most plugins which reference ``Instance`` do so in order to ignore or skip it,
-using a check such as ``if isinstance(node, Instance): return``.
-Such plugins should simply remove consideration of ``Instance`` on pytest>=7.
-However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``,
-and importing it emits a deprecation warning. This will be removed in pytest 8.
+Unfortunately there is no 100% reliable method of identifying when a user has made a mistake, versus when they expect an
+unawaited object from their fixture that they will handle on their own. To suppress this warning
+when you in fact did intend to handle this you can wrap your async fixture in a synchronous fixture:
+
+.. code-block:: python
+
+    import asyncio
+    import pytest
+
+
+    @pytest.fixture
+    async def unawaited_fixture():
+        return 1
+
+
+    def test_foo(unawaited_fixture):
+        assert 1 == asyncio.run(unawaited_fixture)
+
+should be changed to
+
+
+.. code-block:: python
+
+    import asyncio
+    import pytest
+
+
+    @pytest.fixture
+    def unawaited_fixture():
+        async def inner_fixture():
+            return 1
+
+        return inner_fixture()
+
+
+    def test_foo(unawaited_fixture):
+        assert 1 == asyncio.run(unawaited_fixture)
+
+
+You can also make use of `pytest_fixture_setup` to handle the coroutine/asyncgen before pytest sees it - this is the way current async pytest plugins handle it.
+
+If a user has an async fixture with ``autouse=True`` in their ``conftest.py``, or in a file
+containing both synchronous tests and the fixture, they will receive this warning.
+Unless you're using a plugin that specifically handles async fixtures
+with synchronous tests, we strongly recommend against this practice.
+It can lead to unpredictable behavior (with larger scopes, it may appear to "work" if an async
+test is the first to request the fixture, due to value caching) and will generate
+unawaited-coroutine runtime warnings (but only for non-yield fixtures).
+Additionally, it creates ambiguity for other developers about whether the fixture is intended to perform
+setup for synchronous tests.
+
+The `anyio pytest plugin <https://anyio.readthedocs.io/en/stable/testing.html>`_ supports
+synchronous tests with async fixtures, though certain limitations apply.
+
+
+.. _import-or-skip-import-error:
+
+``pytest.importorskip`` default behavior regarding :class:`ImportError`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 8.2
+
+Traditionally :func:`pytest.importorskip` will capture :class:`ImportError`, with the original intent being to skip
+tests where a dependent module is not installed, for example testing with different dependencies.
+
+However some packages might be installed in the system, but are not importable due to
+some other issue, for example, a compilation error or a broken installation. In those cases :func:`pytest.importorskip`
+would still silently skip the test, but more often than not users would like to see the unexpected
+error so the underlying issue can be fixed.
+
+In ``8.2`` the ``exc_type`` parameter has been added, giving users the ability of passing :class:`ModuleNotFoundError`
+to skip tests only if the module cannot really be found, and not because of some other error.
+
+Catching only :class:`ModuleNotFoundError` by default (and letting other errors propagate) would be the best solution,
+however for backward compatibility, pytest will keep the existing behavior but raise an warning if:
+
+1. The captured exception is of type :class:`ImportError`, and:
+2. The user does not pass ``exc_type`` explicitly.
+
+If the import attempt raises :class:`ModuleNotFoundError` (the usual case), then the module is skipped and no
+warning is emitted.
+
+This way, the usual cases will keep working the same way, while unexpected errors will now issue a warning, with
+users being able to suppress the warning by passing ``exc_type=ImportError`` explicitly.
+
+In ``9.0``, the warning will turn into an error, and in ``9.1`` :func:`pytest.importorskip` will only capture
+:class:`ModuleNotFoundError` by default and no warnings will be issued anymore -- but users can still capture
+:class:`ImportError` by passing it to ``exc_type``.
 
 
 .. _node-ctor-fspath-deprecation:
@@ -56,6 +139,10 @@ Plugins which implement custom items and collectors are encouraged to replace
 ``fspath`` parameters (``py.path.local``) with ``path`` parameters
 (``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
 
+If possible, plugins with custom items should use :ref:`cooperative
+constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
+arguments they only pass on to the superclass.
+
 .. note::
     The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
     new attribute being ``path``) is **the opposite** of the situation for
@@ -66,12 +153,54 @@ Plugins which implement custom items and collectors are encouraged to replace
     resolved in future versions as we slowly get rid of the :pypi:`py`
     dependency (see :issue:`9283` for a longer discussion).
 
-Due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo`
+Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`
 which still is expected to return a ``py.path.local`` object, nodes still have
 both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes,
 no matter what argument was used in the constructor. We expect to deprecate the
 ``fspath`` attribute in a future release.
 
+
+Configuring hook specs/impls using markers
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Before pluggy, pytest's plugin library, was its own package and had a clear API,
+pytest just used ``pytest.mark`` to configure hooks.
+
+The :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec` decorators
+have been available since years and should be used instead.
+
+.. code-block:: python
+
+    @pytest.mark.tryfirst
+    def pytest_runtest_call(): ...
+
+
+    # or
+    def pytest_runtest_call(): ...
+
+
+    pytest_runtest_call.tryfirst = True
+
+should be changed to:
+
+.. code-block:: python
+
+    @pytest.hookimpl(tryfirst=True)
+    def pytest_runtest_call(): ...
+
+Changed ``hookimpl`` attributes:
+
+* ``tryfirst``
+* ``trylast``
+* ``optionalhook``
+* ``hookwrapper``
+
+Changed ``hookwrapper`` attributes:
+
+* ``firstresult``
+* ``historic``
+
+
 .. _legacy-path-hooks-deprecated:
 
 ``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
@@ -81,11 +210,11 @@ no matter what argument was used in the constructor. We expect to deprecate the
 
 In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
 
-*  :func:`pytest_ignore_collect(collection_path: pathlib.Path) <_pytest.hookspec.pytest_ignore_collect>` as equivalent to ``path``
-*  :func:`pytest_collect_file(file_path: pathlib.Path) <_pytest.hookspec.pytest_collect_file>` as equivalent to ``path``
-*  :func:`pytest_pycollect_makemodule(module_path: pathlib.Path) <_pytest.hookspec.pytest_pycollect_makemodule>` as equivalent to ``path``
-*  :func:`pytest_report_header(start_path: pathlib.Path) <_pytest.hookspec.pytest_report_header>` as equivalent to ``startdir``
-*  :func:`pytest_report_collectionfinish(start_path: pathlib.Path) <_pytest.hookspec.pytest_report_collectionfinish>` as equivalent to ``startdir``
+*  :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
+*  :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
+*  :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
+*  :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
+*  :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` as equivalent to ``startdir``
 
 The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments.
 
@@ -118,16 +247,284 @@ Directly constructing the following classes is now deprecated:
 
 These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8.
 
-.. _cmdline-preparse-deprecated:
+.. _diamond-inheritance-deprecated:
+
+Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 7.0
+
+Defining a custom pytest node type which is both an :class:`~pytest.Item` and a :class:`~pytest.Collector` (e.g. :class:`~pytest.File`) now issues a warning.
+It was never sanely supported and triggers hard to debug errors.
+
+Some plugins providing linting/code analysis have been using this as a hack.
+Instead, a separate collector node should be used, which collects the item. See
+:ref:`non-python tests` for an example, as well as an `example pr fixing inheritance`_.
+
+.. _example pr fixing inheritance: https://github.com/asmeurer/pytest-flakes/pull/40/files
+
+
+.. _uncooperative-constructors-deprecated:
+
+Constructors of custom :class:`~_pytest.nodes.Node` subclasses should take ``**kwargs``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 7.0
+
+If custom subclasses of nodes like :class:`pytest.Item` override the
+``__init__`` method, they should take ``**kwargs``. Thus,
+
+.. code-block:: python
+
+    class CustomItem(pytest.Item):
+        def __init__(self, name, parent, additional_arg):
+            super().__init__(name, parent)
+            self.additional_arg = additional_arg
+
+should be turned into:
+
+.. code-block:: python
+
+    class CustomItem(pytest.Item):
+        def __init__(self, *, additional_arg, **kwargs):
+            super().__init__(**kwargs)
+            self.additional_arg = additional_arg
+
+to avoid hard-coding the arguments pytest can pass to the superclass.
+See :ref:`non-python tests` for a full example.
+
+For cases without conflicts, no deprecation warning is emitted. For cases with
+conflicts (such as :class:`pytest.File` now taking ``path`` instead of
+``fspath``, as :ref:`outlined above <node-ctor-fspath-deprecation>`), a
+deprecation warning is now raised.
+
+Applying a mark to a fixture function
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 7.4
+
+Applying a mark to a fixture function never had any effect, but it is a common user error.
+
+.. code-block:: python
+
+    @pytest.mark.usefixtures("clean_database")
+    @pytest.fixture
+    def user() -> User: ...
+
+Users expected in this case that the ``usefixtures`` mark would have its intended effect of using the ``clean_database`` fixture when ``user`` was invoked, when in fact it has no effect at all.
+
+Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions.
+
+
+Returning non-None value in test functions
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 7.2
+
+A ``pytest.PytestReturnNotNoneWarning`` is now emitted if a test function returns something other than `None`.
+
+This prevents a common mistake among beginners that expect that returning a `bool` would cause a test to pass or fail, for example:
+
+.. code-block:: python
+
+    @pytest.mark.parametrize(
+        ["a", "b", "result"],
+        [
+            [1, 2, 5],
+            [2, 3, 8],
+            [5, 3, 18],
+        ],
+    )
+    def test_foo(a, b, result):
+        return foo(a, b) == result
+
+Given that pytest ignores the return value, this might be surprising that it will never fail.
+
+The proper fix is to change the `return` to an `assert`:
+
+.. code-block:: python
+
+    @pytest.mark.parametrize(
+        ["a", "b", "result"],
+        [
+            [1, 2, 5],
+            [2, 3, 8],
+            [5, 3, 18],
+        ],
+    )
+    def test_foo(a, b, result):
+        assert foo(a, b) == result
+
+
+The ``yield_fixture`` function/decorator
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 6.2
+
+``pytest.yield_fixture`` is a deprecated alias for :func:`pytest.fixture`.
+
+It has been so for a very long time, so can be search/replaced safely.
+
+
+Removed Features and Breaking Changes
+-------------------------------------
+
+As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
+an appropriate period of deprecation has passed.
+
+Some breaking changes which could not be deprecated are also listed.
+
+.. _yield tests deprecated:
+
+``yield`` tests
+~~~~~~~~~~~~~~~
+
+.. versionremoved:: 4.0
+
+    ``yield`` tests ``xfail``.
+
+.. versionremoved:: 8.4
+
+    ``yield`` tests raise a collection error.
+
+pytest no longer supports ``yield``-style tests, where a test function actually ``yield`` functions and values
+that are then turned into proper test methods. Example:
+
+.. code-block:: python
+
+    def check(x, y):
+        assert x**x == y
+
+
+    def test_squared():
+        yield check, 2, 4
+        yield check, 3, 9
+
+This would result in two actual test functions being generated.
+
+This form of test function doesn't support fixtures properly, and users should switch to ``pytest.mark.parametrize``:
+
+.. code-block:: python
+
+    @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])
+    def test_squared(x, y):
+        assert x**x == y
+
+.. _nose-deprecation:
+
+Support for tests written for nose
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 7.2
+.. versionremoved:: 8.0
+
+Support for running tests written for `nose <https://nose.readthedocs.io/en/latest/>`__ is now deprecated.
+
+``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills
+over the code base (see :issue:`9886` for more details).
+
+setup/teardown
+^^^^^^^^^^^^^^
+
+One thing that might catch users by surprise is that plain ``setup`` and ``teardown`` methods are not pytest native,
+they are in fact part of the ``nose`` support.
+
+
+.. code-block:: python
+
+    class Test:
+        def setup(self):
+            self.resource = make_resource()
+
+        def teardown(self):
+            self.resource.close()
+
+        def test_foo(self): ...
+
+        def test_bar(self): ...
+
+
+
+Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`xunit-method-setup`), so the above should be changed to:
+
+.. code-block:: python
+
+    class Test:
+        def setup_method(self):
+            self.resource = make_resource()
+
+        def teardown_method(self):
+            self.resource.close()
+
+        def test_foo(self): ...
+
+        def test_bar(self): ...
+
+
+This is easy to do in an entire code base by doing a simple find/replace.
+
+@with_setup
+^^^^^^^^^^^
+
+Code using `@with_setup <with-setup-nose>`_ such as this:
+
+.. code-block:: python
+
+    from nose.tools import with_setup
+
+
+    def setup_some_resource(): ...
+
+
+    def teardown_some_resource(): ...
+
+
+    @with_setup(setup_some_resource, teardown_some_resource)
+    def test_foo(): ...
+
+Will also need to be ported to a supported pytest style. One way to do it is using a fixture:
+
+.. code-block:: python
+
+    import pytest
+
+
+    def setup_some_resource(): ...
+
+
+    def teardown_some_resource(): ...
+
+
+    @pytest.fixture
+    def some_resource():
+        setup_some_resource()
+        yield
+        teardown_some_resource()
+
+
+    def test_foo(some_resource): ...
+
+
+.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup
+
+
+The ``compat_co_firstlineno`` attribute
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Nose inspects this attribute on function objects to allow overriding the function's inferred line number.
+Pytest no longer respects this attribute.
+
+
 
 Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 .. deprecated:: 7.0
+.. versionremoved:: 8.0
 
 Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit`
 is now deprecated and ``reason`` should be used instead.  This change is to bring consistency between these
-functions and the``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument.
+functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument.
 
 .. code-block:: python
 
@@ -152,85 +549,56 @@ functions and the``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which
         pytest.exit(reason="bar")
 
 
-Implementing the ``pytest_cmdline_preparse`` hook
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. deprecated:: 7.0
-
-Implementing the :func:`pytest_cmdline_preparse <_pytest.hookspec.pytest_cmdline_preparse>` hook has been officially deprecated.
-Implement the :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load_initial_conftests>` hook instead.
-
-.. code-block:: python
+.. _instance-collector-deprecation:
 
-    def pytest_cmdline_preparse(config: Config, args: List[str]) -> None:
-        ...
+The ``pytest.Instance`` collector
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+.. versionremoved:: 7.0
 
-    # becomes:
+The ``pytest.Instance`` collector type has been removed.
 
+Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`.
+Now :class:`~pytest.Class` collects the test methods directly.
 
-    def pytest_load_initial_conftests(
-        early_config: Config, parser: Parser, args: List[str]
-    ) -> None:
-        ...
+Most plugins which reference ``Instance`` do so in order to ignore or skip it,
+using a check such as ``if isinstance(node, Instance): return``.
+Such plugins should simply remove consideration of ``Instance`` on pytest>=7.
+However, to keep such uses working, a dummy type has been instanced in ``pytest.Instance`` and ``_pytest.python.Instance``,
+and importing it emits a deprecation warning. This was removed in pytest 8.
 
-.. _diamond-inheritance-deprecated:
 
-Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item`
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Using ``pytest.warns(None)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 .. deprecated:: 7.0
+.. versionremoved:: 8.0
 
-Defining a custom pytest node type which is both an :class:`pytest.Item <Item>` and a :class:`pytest.Collector <Collector>` (e.g. :class:`pytest.File <File>`) now issues a warning.
-It was never sanely supported and triggers hard to debug errors.
-
-Some plugins providing linting/code analysis have been using this as a hack.
-Instead, a separate collector node should be used, which collects the item. See
-:ref:`non-python tests` for an example, as well as an `example pr fixing inheritance`_.
+:func:`pytest.warns(None) <pytest.warns>` is now deprecated because it was frequently misused.
+Its correct usage was checking that the code emits at least one warning of any type - like ``pytest.warns()``
+or ``pytest.warns(Warning)``.
 
-.. _example pr fixing inheritance: https://github.com/asmeurer/pytest-flakes/pull/40/files
+See :ref:`warns use cases` for examples.
 
 
 Backward compatibilities in ``Parser.addoption``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 .. deprecated:: 2.4
+.. versionremoved:: 8.0
 
 Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
-scheduled for removal in pytest 8 (deprecated since pytest 2.4.0):
+removed in pytest 8 (deprecated since pytest 2.4.0):
 
 - ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead.
 - ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.
 
 
-Raising ``unittest.SkipTest`` during collection
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. deprecated:: 7.0
-
-Raising :class:`unittest.SkipTest` to skip collection of tests during the
-pytest collection phase is deprecated. Use :func:`pytest.skip` instead.
-
-Note: This deprecation only relates to using `unittest.SkipTest` during test
-collection. You are probably not doing that. Ordinary usage of
-:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` /
-:func:`unittest.skip` in unittest test cases is fully supported.
-
-Using ``pytest.warns(None)``
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. deprecated:: 7.0
-
-:func:`pytest.warns(None) <pytest.warns>` is now deprecated because many people used
-it to mean "this code does not emit warnings", but it actually had the effect of
-checking that the code emits at least one warning of any type - like ``pytest.warns()``
-or ``pytest.warns(Warning)``.
-
-
 The ``--strict`` command-line option
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 .. deprecated:: 6.2
+.. versionremoved:: 8.0
 
 The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which
 better conveys what the option does.
@@ -240,39 +608,172 @@ flag for all strictness related options (``--strict-markers`` and ``--strict-con
 at the moment, more might be introduced in the future).
 
 
-The ``yield_fixture`` function/decorator
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.. _cmdline-preparse-deprecated:
 
-.. deprecated:: 6.2
+Implementing the ``pytest_cmdline_preparse`` hook
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-``pytest.yield_fixture`` is a deprecated alias for :func:`pytest.fixture`.
+.. deprecated:: 7.0
+.. versionremoved:: 8.0
 
-It has been so for a very long time, so can be search/replaced safely.
+Implementing the ``pytest_cmdline_preparse`` hook has been officially deprecated.
+Implement the :hook:`pytest_load_initial_conftests` hook instead.
 
+.. code-block:: python
 
-The ``pytest_warning_captured`` hook
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+    def pytest_cmdline_preparse(config: Config, args: List[str]) -> None: ...
 
-.. deprecated:: 6.0
 
-This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``.
+    # becomes:
+
+
+    def pytest_load_initial_conftests(
+        early_config: Config, parser: Parser, args: List[str]
+    ) -> None: ...
+
+
+Collection changes in pytest 8
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass.
+This is analogous to the existing :class:`pytest.File` for file nodes.
+
+Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`.
+A ``Package`` represents a filesystem directory which is a Python package,
+i.e. contains an ``__init__.py`` file.
+
+:class:`pytest.Package` now only collects files in its own directory; previously it collected recursively.
+Sub-directories are collected as sub-collector nodes, thus creating a collection tree which mirrors the filesystem hierarchy.
+
+:attr:`session.name <pytest.Session.name>` is now ``""``; previously it was the rootdir directory name.
+This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`.
+
+Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`.
+This node represents a filesystem directory, which is not a :class:`pytest.Package`,
+i.e. does not contain an ``__init__.py`` file.
+Similarly to ``Package``, it only collects the files in its own directory,
+while collecting sub-directories as sub-collector nodes.
+
+Files and directories are now collected in alphabetical order jointly, unless changed by a plugin.
+Previously, files were collected before directories.
+
+The collection tree now contains directories/packages up to the :ref:`rootdir <rootdir>`,
+for initial arguments that are found within the rootdir.
+For files outside the rootdir, only the immediate directory/package is collected --
+note however that collecting from outside the rootdir is discouraged.
+
+As an example, given the following filesystem tree::
+
+    myroot/
+        pytest.ini
+        top/
+        ├── aaa
+        │   └── test_aaa.py
+        ├── test_a.py
+        ├── test_b
+        │   ├── __init__.py
+        │   └── test_b.py
+        ├── test_c.py
+        └── zzz
+            ├── __init__.py
+            └── test_zzz.py
+
+the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity,
+is now the following::
+
+    <Session>
+      <Dir myroot>
+        <Dir top>
+          <Dir aaa>
+            <Module test_aaa.py>
+              <Function test_it>
+          <Module test_a.py>
+            <Function test_it>
+          <Package test_b>
+            <Module test_b.py>
+              <Function test_it>
+          <Module test_c.py>
+            <Function test_it>
+          <Package zzz>
+            <Module test_zzz.py>
+              <Function test_it>
+
+Previously, it was::
+
+    <Session>
+      <Module top/test_a.py>
+        <Function test_it>
+      <Module top/test_c.py>
+        <Function test_it>
+      <Module top/aaa/test_aaa.py>
+        <Function test_it>
+      <Package test_b>
+        <Module test_b.py>
+          <Function test_it>
+      <Package zzz>
+        <Module test_zzz.py>
+          <Function test_it>
+
+Code/plugins which rely on a specific shape of the collection tree might need to update.
+
+
+:class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File`
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionchanged:: 8.0
+
+The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file.
+Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module),
+the module being the `__init__.py` file.
+This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details).
+
+The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file.
+
+Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist,
+if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files).
+
+
+Collecting ``__init__.py`` files no longer collects package
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. versionremoved:: 8.0
+
+Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
+Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
+(unless :confval:`python_files` was changed to allow `__init__.py` file).
+
+To collect the entire package, specify just the directory: `pytest pkg`.
 
-Use the ``pytest_warning_recored`` hook instead, which replaces the ``item`` parameter
-by a ``nodeid`` parameter.
 
 The ``pytest.collect`` module
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 .. deprecated:: 6.0
+.. versionremoved:: 7.0
 
 The ``pytest.collect`` module is no longer part of the public API, all its names
 should now be imported from ``pytest`` directly instead.
 
 
+
+The ``pytest_warning_captured`` hook
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 6.0
+.. versionremoved:: 7.0
+
+This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``.
+
+Use the ``pytest_warning_recorded`` hook instead, which replaces the ``item`` parameter
+by a ``nodeid`` parameter.
+
+
+
 The ``pytest._fillfuncargs`` function
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 .. deprecated:: 6.0
+.. versionremoved:: 7.0
 
 This function was kept for backward compatibility with an older plugin.
 
@@ -281,12 +782,6 @@ it, use `function._request._fillfixtures()` instead, though note this is not
 a public API and may break in the future.
 
 
-Removed Features
-----------------
-
-As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
-an appropriate period of deprecation has passed.
-
 ``--no-print-logs`` command-line option
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -324,8 +819,8 @@ at some point, depending on the plans for the plugins and number of users using
 
 .. versionremoved:: 6.0
 
-The ``pytest_collect_directory`` has not worked properly for years (it was called
-but the results were ignored). Users may consider using :func:`pytest_collection_modifyitems <_pytest.hookspec.pytest_collection_modifyitems>` instead.
+The ``pytest_collect_directory`` hook has not worked properly for years (it was called
+but the results were ignored). Users may consider using :hook:`pytest_collection_modifyitems` instead.
 
 TerminalReporter.writer
 ~~~~~~~~~~~~~~~~~~~~~~~
@@ -372,7 +867,7 @@ By using ``legacy`` you will keep using the legacy/xunit1 format when upgrading
 pytest 6.0, where the default format will be ``xunit2``.
 
 In order to let users know about the transition, pytest will issue a warning in case
-the ``--junitxml`` option is given in the command line but ``junit_family`` is not explicitly
+the ``--junit-xml`` option is given in the command line but ``junit_family`` is not explicitly
 configured in ``pytest.ini``.
 
 Services known to support the ``xunit2`` format:
@@ -549,8 +1044,7 @@ Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated
             (50, 500),
         ],
     )
-    def test_foo(a, b):
-        ...
+    def test_foo(a, b): ...
 
 This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
 call.
@@ -573,8 +1067,7 @@ To update the code, use ``pytest.param``:
             (50, 500),
         ],
     )
-    def test_foo(a, b):
-        ...
+    def test_foo(a, b): ...
 
 
 .. _pytest_funcarg__ prefix deprecated:
@@ -725,15 +1218,13 @@ This is just a matter of renaming the fixture as the API is the same:
 
 .. code-block:: python
 
-    def test_foo(record_xml_property):
-        ...
+    def test_foo(record_xml_property): ...
 
 Change to:
 
 .. code-block:: python
 
-    def test_foo(record_property):
-        ...
+    def test_foo(record_property): ...
 
 
 .. _passing command-line string to pytest.main deprecated:
@@ -815,36 +1306,6 @@ with the ``name`` parameter:
         return cell()
 
 
-.. _yield tests deprecated:
-
-``yield`` tests
-~~~~~~~~~~~~~~~
-
-.. versionremoved:: 4.0
-
-pytest supported ``yield``-style tests, where a test function actually ``yield`` functions and values
-that are then turned into proper test methods. Example:
-
-.. code-block:: python
-
-    def check(x, y):
-        assert x ** x == y
-
-
-    def test_squared():
-        yield check, 2, 4
-        yield check, 3, 9
-
-This would result into two actual test functions being generated.
-
-This form of test function doesn't support fixtures properly, and users should switch to ``pytest.mark.parametrize``:
-
-.. code-block:: python
-
-    @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])
-    def test_squared(x, y):
-        assert x ** x == y
-
 .. _internal classes accessed through node deprecated:
 
 Internal classes accessed through ``Node``
@@ -895,8 +1356,7 @@ Example of usage:
 
 .. code-block:: python
 
-    class MySymbol:
-        ...
+    class MySymbol: ...
 
 
     def pytest_namespace():
diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py
index abb9bce5097..16a578fda12 100644
--- a/doc/en/example/assertion/failure_demo.py
+++ b/doc/en/example/assertion/failure_demo.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import pytest
 from pytest import raises
 
@@ -172,7 +174,7 @@ def test_raise(self):
         raise ValueError("demo error")
 
     def test_tupleerror(self):
-        a, b = [1]  # NOQA
+        a, b = [1]  # noqa: F841
 
     def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
         items = [1, 2, 3]
@@ -180,7 +182,7 @@ def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
         a, b = items.pop()
 
     def test_some_error(self):
-        if namenotexi:  # NOQA
+        if namenotexi:  # noqa: F821
             pass
 
     def func1(self):
@@ -265,9 +267,9 @@ class A:
             a = 1
 
         b = 2
-        assert (
-            A.a == b
-        ), "A.a appears not to be b\nor does not appear to be b\none of those"
+        assert A.a == b, (
+            "A.a appears not to be b\nor does not appear to be b\none of those"
+        )
 
     def test_custom_repr(self):
         class JSON:
diff --git a/doc/en/example/assertion/global_testmodule_config/conftest.py b/doc/en/example/assertion/global_testmodule_config/conftest.py
index 7cdf18cdbc1..835726473ba 100644
--- a/doc/en/example/assertion/global_testmodule_config/conftest.py
+++ b/doc/en/example/assertion/global_testmodule_config/conftest.py
@@ -1,7 +1,10 @@
+from __future__ import annotations
+
 import os.path
 
 import pytest
 
+
 mydir = os.path.dirname(__file__)
 
 
diff --git a/doc/en/example/assertion/global_testmodule_config/test_hello_world.py b/doc/en/example/assertion/global_testmodule_config/test_hello_world.py
index a31a601a1ce..e3c927316f9 100644
--- a/doc/en/example/assertion/global_testmodule_config/test_hello_world.py
+++ b/doc/en/example/assertion/global_testmodule_config/test_hello_world.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+
 hello = "world"
 
 
diff --git a/doc/en/example/assertion/test_failures.py b/doc/en/example/assertion/test_failures.py
index 350518b43c7..17373f62213 100644
--- a/doc/en/example/assertion/test_failures.py
+++ b/doc/en/example/assertion/test_failures.py
@@ -1,6 +1,9 @@
+from __future__ import annotations
+
 import os.path
 import shutil
 
+
 failure_demo = os.path.join(os.path.dirname(__file__), "failure_demo.py")
 pytest_plugins = ("pytester",)
 
diff --git a/doc/en/example/assertion/test_setup_flow_example.py b/doc/en/example/assertion/test_setup_flow_example.py
index 0e7eded06b6..fe11c2bf3f2 100644
--- a/doc/en/example/assertion/test_setup_flow_example.py
+++ b/doc/en/example/assertion/test_setup_flow_example.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+
 def setup_module(module):
     module.TestStateFullThing.classcount = 0
 
diff --git a/doc/en/example/attic.rst b/doc/en/example/attic.rst
index 2ea87006204..2b1f2766dce 100644
--- a/doc/en/example/attic.rst
+++ b/doc/en/example/attic.rst
@@ -25,7 +25,7 @@ example: specifying and selecting acceptance tests
             self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)
 
         def run(self, *cmd):
-            """ called by test code to execute an acceptance test. """
+            """called by test code to execute an acceptance test."""
             self.tmpdir.chdir()
             return subprocess.check_output(cmd).decode()
 
diff --git a/doc/en/example/conftest.py b/doc/en/example/conftest.py
index f905738c4f6..21c9a489961 100644
--- a/doc/en/example/conftest.py
+++ b/doc/en/example/conftest.py
@@ -1 +1,4 @@
-collect_ignore = ["nonpython"]
+from __future__ import annotations
+
+
+collect_ignore = ["nonpython", "customdirectory"]
diff --git a/doc/en/example/customdirectory.rst b/doc/en/example/customdirectory.rst
new file mode 100644
index 00000000000..1e4d7e370de
--- /dev/null
+++ b/doc/en/example/customdirectory.rst
@@ -0,0 +1,77 @@
+.. _`custom directory collectors`:
+
+Using a custom directory collector
+====================================================
+
+By default, pytest collects directories using :class:`pytest.Package`, for directories with ``__init__.py`` files,
+and :class:`pytest.Dir` for other directories.
+If you want to customize how a directory is collected, you can write your own :class:`pytest.Directory` collector,
+and use :hook:`pytest_collect_directory` to hook it up.
+
+.. _`directory manifest plugin`:
+
+A basic example for a directory manifest file
+--------------------------------------------------------------
+
+Suppose you want to customize how collection is done on a per-directory basis.
+Here is an example ``conftest.py`` plugin that allows directories to contain a ``manifest.json`` file,
+which defines how the collection should be done for the directory.
+In this example, only a simple list of files is supported,
+however you can imagine adding other keys, such as exclusions and globs.
+
+.. include:: customdirectory/conftest.py
+    :literal:
+
+You can create a ``manifest.json`` file and some test files:
+
+.. include:: customdirectory/tests/manifest.json
+    :literal:
+
+.. include:: customdirectory/tests/test_first.py
+    :literal:
+
+.. include:: customdirectory/tests/test_second.py
+    :literal:
+
+.. include:: customdirectory/tests/test_third.py
+    :literal:
+
+An you can now execute the test specification:
+
+.. code-block:: pytest
+
+    customdirectory $ pytest
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
+    rootdir: /home/sweet/project/customdirectory
+    configfile: pytest.ini
+    collected 2 items
+
+    tests/test_first.py .                                                [ 50%]
+    tests/test_second.py .                                               [100%]
+
+    ============================ 2 passed in 0.12s =============================
+
+.. regendoc:wipe
+
+Notice how ``test_three.py`` was not executed, because it is not listed in the manifest.
+
+You can verify that your custom collector appears in the collection tree:
+
+.. code-block:: pytest
+
+    customdirectory $ pytest --collect-only
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
+    rootdir: /home/sweet/project/customdirectory
+    configfile: pytest.ini
+    collected 2 items
+
+    <Dir customdirectory>
+      <ManifestDirectory tests>
+        <Module test_first.py>
+          <Function test_1>
+        <Module test_second.py>
+          <Function test_2>
+
+    ======================== 2 tests collected in 0.12s ========================
diff --git a/doc/en/example/customdirectory/conftest.py b/doc/en/example/customdirectory/conftest.py
new file mode 100644
index 00000000000..ea922e04723
--- /dev/null
+++ b/doc/en/example/customdirectory/conftest.py
@@ -0,0 +1,30 @@
+# content of conftest.py
+from __future__ import annotations
+
+import json
+
+import pytest
+
+
+class ManifestDirectory(pytest.Directory):
+    def collect(self):
+        # The standard pytest behavior is to loop over all `test_*.py` files and
+        # call `pytest_collect_file` on each file. This collector instead reads
+        # the `manifest.json` file and only calls `pytest_collect_file` for the
+        # files defined there.
+        manifest_path = self.path / "manifest.json"
+        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
+        ihook = self.ihook
+        for file in manifest["files"]:
+            yield from ihook.pytest_collect_file(
+                file_path=self.path / file, parent=self
+            )
+
+
+@pytest.hookimpl
+def pytest_collect_directory(path, parent):
+    # Use our custom collector for directories containing a `manifest.json` file.
+    if path.joinpath("manifest.json").is_file():
+        return ManifestDirectory.from_parent(parent=parent, path=path)
+    # Otherwise fallback to the standard behavior.
+    return None
diff --git a/doc/en/example/customdirectory/pytest.ini b/doc/en/example/customdirectory/pytest.ini
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/doc/en/example/customdirectory/tests/manifest.json b/doc/en/example/customdirectory/tests/manifest.json
new file mode 100644
index 00000000000..6ab6d0a5222
--- /dev/null
+++ b/doc/en/example/customdirectory/tests/manifest.json
@@ -0,0 +1,6 @@
+{
+    "files": [
+        "test_first.py",
+        "test_second.py"
+    ]
+}
diff --git a/doc/en/example/customdirectory/tests/test_first.py b/doc/en/example/customdirectory/tests/test_first.py
new file mode 100644
index 00000000000..9953dd37785
--- /dev/null
+++ b/doc/en/example/customdirectory/tests/test_first.py
@@ -0,0 +1,6 @@
+# content of test_first.py
+from __future__ import annotations
+
+
+def test_1():
+    pass
diff --git a/doc/en/example/customdirectory/tests/test_second.py b/doc/en/example/customdirectory/tests/test_second.py
new file mode 100644
index 00000000000..df264f48b3b
--- /dev/null
+++ b/doc/en/example/customdirectory/tests/test_second.py
@@ -0,0 +1,6 @@
+# content of test_second.py
+from __future__ import annotations
+
+
+def test_2():
+    pass
diff --git a/doc/en/example/customdirectory/tests/test_third.py b/doc/en/example/customdirectory/tests/test_third.py
new file mode 100644
index 00000000000..b8b072dd770
--- /dev/null
+++ b/doc/en/example/customdirectory/tests/test_third.py
@@ -0,0 +1,6 @@
+# content of test_third.py
+from __future__ import annotations
+
+
+def test_3():
+    pass
diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse.py b/doc/en/example/fixtures/test_fixtures_order_autouse.py
index ec282ab4b2b..04cbc268b7f 100644
--- a/doc/en/example/fixtures/test_fixtures_order_autouse.py
+++ b/doc/en/example/fixtures/test_fixtures_order_autouse.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_flat.svg b/doc/en/example/fixtures/test_fixtures_order_autouse_flat.svg
new file mode 100644
index 00000000000..03c4598272a
--- /dev/null
+++ b/doc/en/example/fixtures/test_fixtures_order_autouse_flat.svg
@@ -0,0 +1,56 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="112" height="682">
+    <style>
+        text {
+            font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
+            dominant-baseline: middle;
+            text-anchor: middle;
+            fill: #062886;
+            font-size: medium;
+        }
+        ellipse.fixture, rect.test {
+            fill: #eeffcc;
+            stroke: #007020;
+            stroke-width: 2;
+        }
+        text.fixture {
+            color: #06287e;
+        }
+        circle.class {
+            fill: #c3e0ec;
+            stroke: #0e84b5;
+            stroke-width: 2;
+        }
+        text.class {
+            fill: #0e84b5;
+        }
+        path, line {
+            stroke: black;
+            stroke-width: 2;
+            fill: none;
+        }
+        rect.autouse {
+            fill: #ca7f3d;
+        }
+    </style>
+    <line x1="56" x2="56" y1="681" y2="26" />
+    <ellipse class="fixture" rx="50" ry="25" cx="56" cy="26" />
+    <text x="56" y="26">order</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="96" />
+    <text x="56" y="96">a</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="166" />
+    <text x="56" y="166">b</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="236" />
+    <text x="56" y="236">c</text>
+    <rect class="autouse" width="112" height="40" x="0" y="286" />
+    <text x="56" y="306">autouse</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="376" />
+    <text x="56" y="376">d</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="446" />
+    <text x="56" y="446">e</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="516" />
+    <text x="56" y="516">f</text>
+    <ellipse class="fixture" rx="25" ry="25" cx="56" cy="586" />
+    <text x="56" y="586">g</text>
+    <rect class="test" width="110" height="50" x="1" y="631" />
+    <text x="56" y="656">test_order</text>
+</svg>
diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py
index de0c2642793..828fa4cf6d6 100644
--- a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py
+++ b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py
index ba01ad32f57..ebd5d10f5bb 100644
--- a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py
+++ b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/doc/en/example/fixtures/test_fixtures_order_dependencies.py
index b3512c2a64d..1c59f010341 100644
--- a/doc/en/example/fixtures/test_fixtures_order_dependencies.py
+++ b/doc/en/example/fixtures/test_fixtures_order_dependencies.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import pytest
 
 
@@ -17,7 +19,7 @@ def b(a, order):
 
 
 @pytest.fixture
-def c(a, b, order):
+def c(b, order):
     order.append("c")
 
 
diff --git a/doc/en/example/fixtures/test_fixtures_order_scope.py b/doc/en/example/fixtures/test_fixtures_order_scope.py
index 5d9487cab34..4b4260fbdcd 100644
--- a/doc/en/example/fixtures/test_fixtures_order_scope.py
+++ b/doc/en/example/fixtures/test_fixtures_order_scope.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/doc/en/example/fixtures/test_fixtures_request_different_scope.py b/doc/en/example/fixtures/test_fixtures_request_different_scope.py
index 00e2e46d845..dee61f8c4d7 100644
--- a/doc/en/example/fixtures/test_fixtures_request_different_scope.py
+++ b/doc/en/example/fixtures/test_fixtures_request_different_scope.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/doc/en/example/index.rst b/doc/en/example/index.rst
index 71e855534ff..840819002d4 100644
--- a/doc/en/example/index.rst
+++ b/doc/en/example/index.rst
@@ -18,7 +18,6 @@ For basic examples, see
 - :ref:`Fixtures <fixtures>` for basic fixture/setup examples
 - :ref:`parametrize` for basic test function parametrization
 - :ref:`unittest` for basic unittest integration
-- :ref:`noseintegration` for basic nosetests integration
 
 The following examples aim at various use cases you might encounter.
 
@@ -32,3 +31,4 @@ The following examples aim at various use cases you might encounter.
    special
    pythoncollection
    nonpython
+   customdirectory
diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst
index 402b8e34626..babcd9e2f3a 100644
--- a/doc/en/example/markers.rst
+++ b/doc/en/example/markers.rst
@@ -25,10 +25,12 @@ You can "mark" a test function with custom metadata like this:
         pass  # perform some webtest test for your app
 
 
+    @pytest.mark.device(serial="123")
     def test_something_quick():
         pass
 
 
+    @pytest.mark.device(serial="abc")
     def test_another():
         pass
 
@@ -45,7 +47,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:
 
     $ pytest -v -m webtest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 4 items / 3 deselected / 1 selected
@@ -60,7 +62,7 @@ Or the inverse, running all tests except the webtest ones:
 
     $ pytest -v -m "not webtest"
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 4 items / 1 deselected / 3 selected
@@ -71,6 +73,28 @@ Or the inverse, running all tests except the webtest ones:
 
     ===================== 3 passed, 1 deselected in 0.12s ======================
 
+.. _`marker_keyword_expression_example`:
+
+Additionally, you can restrict a test run to only run tests matching one or multiple marker
+keyword arguments, e.g. to run only tests marked with ``device`` and the specific ``serial="123"``:
+
+.. code-block:: pytest
+
+    $ pytest -v -m "device(serial='123')"
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    cachedir: .pytest_cache
+    rootdir: /home/sweet/project
+    collecting ... collected 4 items / 3 deselected / 1 selected
+
+    test_server.py::test_something_quick PASSED                          [100%]
+
+    ===================== 1 passed, 3 deselected in 0.12s ======================
+
+.. note:: Only keyword argument matching is supported in marker expressions.
+
+.. note:: Only :class:`int`, (unescaped) :class:`str`, :class:`bool` & :data:`None` values are supported in marker expressions.
+
 Selecting tests based on their node ID
 --------------------------------------
 
@@ -82,7 +106,7 @@ tests based on their module, class, method, or function name:
 
     $ pytest -v test_server.py::TestClass::test_method
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 1 item
@@ -97,7 +121,7 @@ You can also select on the class:
 
     $ pytest -v test_server.py::TestClass
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 1 item
@@ -112,7 +136,7 @@ Or select multiple nodes:
 
     $ pytest -v test_server.py::TestClass test_server.py::test_send_http
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 2 items
@@ -136,7 +160,7 @@ Or select multiple nodes:
 
     Node IDs for failing tests are displayed in the test summary info
     when running pytest with the ``-rf`` option.  You can also
-    construct Node IDs from the output of ``pytest --collectonly``.
+    construct Node IDs from the output of ``pytest --collect-only``.
 
 Using ``-k expr`` to select tests based on their name
 -------------------------------------------------------
@@ -156,7 +180,7 @@ The expression matching is now case-insensitive.
 
     $ pytest -v -k http  # running with the above defined example module
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 4 items / 3 deselected / 1 selected
@@ -171,7 +195,7 @@ And you can also run all tests except the ones that match the keyword:
 
     $ pytest -k "not send_http" -v
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 4 items / 1 deselected / 3 selected
@@ -188,7 +212,7 @@ Or to select "http" and "quick" tests:
 
     $ pytest -k "http or quick" -v
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 4 items / 2 deselected / 2 selected
@@ -246,9 +270,9 @@ You can ask which markers exist for your test suite - the list includes our just
 
     @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures
 
-    @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
+    @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.
 
-    @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
+    @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead.
 
 
 For an example on how to add and work with markers from a plugin, see
@@ -346,7 +370,7 @@ Custom marker and command line option to control test runs
 Plugins can provide custom markers and implement specific behaviour
 based on it. This is a self-contained example which adds a command
 line option and a parametrized test function marker to run tests
-specifies via named environments:
+specified via named environments:
 
 .. code-block:: python
 
@@ -375,7 +399,7 @@ specifies via named environments:
         envnames = [mark.args[0] for mark in item.iter_markers(name="env")]
         if envnames:
             if item.config.getoption("-E") not in envnames:
-                pytest.skip("test requires env in {!r}".format(envnames))
+                pytest.skip(f"test requires env in {envnames!r}")
 
 A test file using this local plugin:
 
@@ -397,8 +421,7 @@ the test needs:
 
     $ pytest -E stage2
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 1 item
 
@@ -412,8 +435,7 @@ and here is one that specifies exactly the environment needed:
 
     $ pytest -E stage1
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 1 item
 
@@ -440,9 +462,9 @@ The ``--markers`` option always gives you a list of available markers:
 
     @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures
 
-    @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
+    @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.
 
-    @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
+    @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead.
 
 
 .. _`passing callables to custom markers`:
@@ -530,7 +552,7 @@ test function.  From a conftest file we can read it like this:
 
     def pytest_runtest_setup(item):
         for mark in item.iter_markers(name="glob"):
-            print("glob args={} kwargs={}".format(mark.args, mark.kwargs))
+            print(f"glob args={mark.args} kwargs={mark.kwargs}")
             sys.stdout.flush()
 
 Let's run this without capturing output and see what we get:
@@ -560,6 +582,7 @@ for your particular platform, you could use the following plugin:
     # content of conftest.py
     #
     import sys
+
     import pytest
 
     ALL = set("darwin linux win32".split())
@@ -569,7 +592,7 @@ for your particular platform, you could use the following plugin:
         supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers())
         plat = sys.platform
         if supported_platforms and plat not in supported_platforms:
-            pytest.skip("cannot run on platform {}".format(plat))
+            pytest.skip(f"cannot run on platform {plat}")
 
 then tests will be skipped if they were specified for a different platform.
 Let's do a little test file to show how this looks like:
@@ -605,15 +628,14 @@ then you will see two tests skipped and two executed tests as expected:
 
     $ pytest -rs # this option reports skip reasons
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 4 items
 
     test_plat.py s.s.                                                    [100%]
 
     ========================= short test summary info ==========================
-    SKIPPED [2] conftest.py:12: cannot run on platform linux
+    SKIPPED [2] conftest.py:13: cannot run on platform linux
     ======================= 2 passed, 2 skipped in 0.12s =======================
 
 Note that if you specify a platform via the marker-command line option like this:
@@ -622,8 +644,7 @@ Note that if you specify a platform via the marker-command line option like this
 
     $ pytest -m linux
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 4 items / 3 deselected / 1 selected
 
@@ -686,8 +707,7 @@ We can now use the ``-m option`` to select one set:
 
     $ pytest -m interface --tb=short
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 4 items / 2 deselected / 2 selected
 
@@ -713,8 +733,7 @@ or to select both "event" and "interface" tests:
 
     $ pytest -m "interface or event" --tb=short
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 4 items / 1 deselected / 3 selected
 
diff --git a/doc/en/example/multipython.py b/doc/en/example/multipython.py
index 9005d31addd..f54524213bc 100644
--- a/doc/en/example/multipython.py
+++ b/doc/en/example/multipython.py
@@ -1,14 +1,16 @@
-"""
-module containing a parametrized tests testing cross-python
-serialization via the pickle module.
-"""
+"""Module containing a parametrized tests testing cross-python serialization
+via the pickle module."""
+
+from __future__ import annotations
+
 import shutil
 import subprocess
 import textwrap
 
 import pytest
 
-pythonlist = ["python3.5", "python3.6", "python3.7"]
+
+pythonlist = ["python3.9", "python3.10", "python3.11"]
 
 
 @pytest.fixture(params=pythonlist)
@@ -33,37 +35,33 @@ def dumps(self, obj):
         dumpfile = self.picklefile.with_name("dump.py")
         dumpfile.write_text(
             textwrap.dedent(
-                r"""
+                rf"""
                 import pickle
-                f = open({!r}, 'wb')
-                s = pickle.dump({!r}, f, protocol=2)
+                f = open({str(self.picklefile)!r}, 'wb')
+                s = pickle.dump({obj!r}, f, protocol=2)
                 f.close()
-                """.format(
-                    str(self.picklefile), obj
-                )
+                """
             )
         )
-        subprocess.check_call((self.pythonpath, str(dumpfile)))
+        subprocess.run((self.pythonpath, str(dumpfile)), check=True)
 
     def load_and_is_true(self, expression):
         loadfile = self.picklefile.with_name("load.py")
         loadfile.write_text(
             textwrap.dedent(
-                r"""
+                rf"""
                 import pickle
-                f = open({!r}, 'rb')
+                f = open({str(self.picklefile)!r}, 'rb')
                 obj = pickle.load(f)
                 f.close()
-                res = eval({!r})
+                res = eval({expression!r})
                 if not res:
                     raise SystemExit(1)
-                """.format(
-                    str(self.picklefile), expression
-                )
+                """
             )
         )
         print(loadfile)
-        subprocess.check_call((self.pythonpath, str(loadfile)))
+        subprocess.run((self.pythonpath, str(loadfile)), check=True)
 
 
 @pytest.mark.parametrize("obj", [42, {}, {1: 3}])
diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst
index 876c9c872f1..aa463e2416b 100644
--- a/doc/en/example/nonpython.rst
+++ b/doc/en/example/nonpython.rst
@@ -9,7 +9,7 @@ Working with non-python tests
 A basic example for specifying tests in Yaml files
 --------------------------------------------------------------
 
-.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py
+.. _`pytest-yamlwsgi`: https://pypi.org/project/pytest-yamlwsgi/
 
 Here is an example ``conftest.py`` (extracted from Ali Afshar's special purpose `pytest-yamlwsgi`_ plugin).   This ``conftest.py`` will  collect ``test*.yaml`` files and will execute the yaml-formatted content as custom tests:
 
@@ -28,8 +28,7 @@ now execute the test specification:
 
     nonpython $ pytest test_simple.yaml
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project/nonpython
     collected 2 items
 
@@ -65,7 +64,7 @@ consulted when reporting in ``verbose`` mode:
 
     nonpython $ pytest -v
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project/nonpython
     collecting ... collected 2 items
@@ -91,8 +90,7 @@ interesting to just look at the collection tree:
 
     nonpython $ pytest --collect-only
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project/nonpython
     collected 2 items
 
diff --git a/doc/en/example/nonpython/conftest.py b/doc/en/example/nonpython/conftest.py
index 7ec4134036c..b7bdc77a004 100644
--- a/doc/en/example/nonpython/conftest.py
+++ b/doc/en/example/nonpython/conftest.py
@@ -1,4 +1,6 @@
 # content of conftest.py
+from __future__ import annotations
+
 import pytest
 
 
@@ -12,14 +14,14 @@ def collect(self):
         # We need a yaml parser, e.g. PyYAML.
         import yaml
 
-        raw = yaml.safe_load(self.path.open())
+        raw = yaml.safe_load(self.path.open(encoding="utf-8"))
         for name, spec in sorted(raw.items()):
             yield YamlItem.from_parent(self, name=name, spec=spec)
 
 
 class YamlItem(pytest.Item):
-    def __init__(self, name, parent, spec):
-        super().__init__(name, parent)
+    def __init__(self, *, spec, **kwargs):
+        super().__init__(**kwargs)
         self.spec = spec
 
     def runtest(self):
@@ -38,6 +40,7 @@ def repr_failure(self, excinfo):
                     "   no further details known at this point.",
                 ]
             )
+        return super().repr_failure(excinfo)
 
     def reportinfo(self):
         return self.path, 0, f"usecase: {self.name}"
diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst
index 87ed1420694..69e715c9db1 100644
--- a/doc/en/example/parametrize.rst
+++ b/doc/en/example/parametrize.rst
@@ -4,8 +4,6 @@
 Parametrizing tests
 =================================================
 
-.. currentmodule:: _pytest.python
-
 ``pytest`` allows to easily parametrize test functions.
 For basic docs, see :ref:`parametrize-basics`.
 
@@ -160,20 +158,20 @@ objects, they are still using the default pytest representation:
 
     $ pytest test_time.py --collect-only
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 8 items
 
-    <Module test_time.py>
-      <Function test_timedistance_v0[a0-b0-expected0]>
-      <Function test_timedistance_v0[a1-b1-expected1]>
-      <Function test_timedistance_v1[forward]>
-      <Function test_timedistance_v1[backward]>
-      <Function test_timedistance_v2[20011212-20011211-expected0]>
-      <Function test_timedistance_v2[20011211-20011212-expected1]>
-      <Function test_timedistance_v3[forward]>
-      <Function test_timedistance_v3[backward]>
+    <Dir parametrize.rst-206>
+      <Module test_time.py>
+        <Function test_timedistance_v0[a0-b0-expected0]>
+        <Function test_timedistance_v0[a1-b1-expected1]>
+        <Function test_timedistance_v1[forward]>
+        <Function test_timedistance_v1[backward]>
+        <Function test_timedistance_v2[20011212-20011211-expected0]>
+        <Function test_timedistance_v2[20011211-20011212-expected1]>
+        <Function test_timedistance_v3[forward]>
+        <Function test_timedistance_v3[backward]>
 
     ======================== 8 tests collected in 0.12s ========================
 
@@ -186,7 +184,7 @@ A quick port of "testscenarios"
 Here is a quick port to run tests configured with :pypi:`testscenarios`,
 an add-on from Robert Collins for the standard unittest framework. We
 only have to work a bit to construct the correct arguments for pytest's
-:py:func:`Metafunc.parametrize`:
+:py:func:`Metafunc.parametrize <pytest.Metafunc.parametrize>`:
 
 .. code-block:: python
 
@@ -223,8 +221,7 @@ this is a fully self-contained example which you can run with:
 
     $ pytest test_scenarios.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 4 items
 
@@ -238,13 +235,13 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
 
     $ pytest --collect-only test_scenarios.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 4 items
 
-    <Module test_scenarios.py>
-      <Class TestSampleWithScenarios>
+    <Dir parametrize.rst-206>
+      <Module test_scenarios.py>
+        <Class TestSampleWithScenarios>
           <Function test_demo1[basic]>
           <Function test_demo2[basic]>
           <Function test_demo1[advanced]>
@@ -317,14 +314,14 @@ Let's first see how it looks like at collection time:
 
     $ pytest test_backends.py --collect-only
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
 
-    <Module test_backends.py>
-      <Function test_db_initialized[d1]>
-      <Function test_db_initialized[d2]>
+    <Dir parametrize.rst-206>
+      <Module test_backends.py>
+        <Function test_db_initialized[d1]>
+        <Function test_db_initialized[d2]>
 
     ======================== 2 tests collected in 0.12s ========================
 
@@ -416,7 +413,7 @@ The result of this test will be successful:
 
     $ pytest -v test_indirect_list.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 1 item
@@ -487,8 +484,8 @@ argument sets to use for each test function.  Let's run it:
     FAILED test_parametrize.py::TestClass::test_equals[1-2] - assert 1 == 2
     1 failed, 2 passed in 0.12s
 
-Indirect parametrization with multiple fixtures
---------------------------------------------------------------
+Parametrization with multiple fixtures
+--------------------------------------
 
 Here is a stripped down real-life example of using parametrized
 testing for testing serialization of objects between different python
@@ -506,11 +503,15 @@ Running it results in some skips if we don't have all the python interpreters in
 .. code-block:: pytest
 
    . $ pytest -rs -q multipython.py
-   ...........................                                          [100%]
-   27 passed in 0.12s
-
-Indirect parametrization of optional implementations/imports
---------------------------------------------------------------------
+   sssssssssssssssssssssssssss                                          [100%]
+   ========================= short test summary info ==========================
+   SKIPPED [9] multipython.py:67: 'python3.9' not found
+   SKIPPED [9] multipython.py:67: 'python3.10' not found
+   SKIPPED [9] multipython.py:67: 'python3.11' not found
+   27 skipped in 0.12s
+
+Parametrization of optional implementations/imports
+---------------------------------------------------
 
 If you want to compare the outcomes of several implementations of a given
 API, you can write test functions that receive the already imported implementations
@@ -567,15 +568,14 @@ If you run this with reporting for skips enabled:
 
     $ pytest -rs test_module.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
 
     test_module.py .s                                                    [100%]
 
     ========================= short test summary info ==========================
-    SKIPPED [1] conftest.py:12: could not import 'opt2': No module named 'opt2'
+    SKIPPED [1] test_module.py:3: could not import 'opt2': No module named 'opt2'
     ======================= 1 passed, 1 skipped in 0.12s =======================
 
 You'll see that we don't have an ``opt2`` module and thus the second test run
@@ -629,7 +629,7 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
 
     $ pytest -v -m basic
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 24 items / 21 deselected / 3 selected
@@ -658,52 +658,34 @@ Use :func:`pytest.raises` with the
 :ref:`pytest.mark.parametrize ref` decorator to write parametrized tests
 in which some tests raise exceptions and others do not.
 
-It is helpful to define a no-op context manager ``does_not_raise`` to serve
-as a complement to ``raises``. For example:
+``contextlib.nullcontext`` can be used to test cases that are not expected to
+raise exceptions but that should result in some value. The value is given as the
+``enter_result`` parameter, which will be available as the ``with`` statement’s
+target (``e`` in the example below).
 
-.. code-block:: python
+For example:
 
-    from contextlib import contextmanager
-    import pytest
+.. code-block:: python
 
+    from contextlib import nullcontext
 
-    @contextmanager
-    def does_not_raise():
-        yield
+    import pytest
 
 
     @pytest.mark.parametrize(
         "example_input,expectation",
         [
-            (3, does_not_raise()),
-            (2, does_not_raise()),
-            (1, does_not_raise()),
+            (3, nullcontext(2)),
+            (2, nullcontext(3)),
+            (1, nullcontext(6)),
             (0, pytest.raises(ZeroDivisionError)),
         ],
     )
     def test_division(example_input, expectation):
         """Test how much I know division."""
-        with expectation:
-            assert (6 / example_input) is not None
-
-In the example above, the first three test cases should run unexceptionally,
-while the fourth should raise ``ZeroDivisionError``.
-
-If you're only supporting Python 3.7+, you can simply use ``nullcontext``
-to define ``does_not_raise``:
-
-.. code-block:: python
-
-    from contextlib import nullcontext as does_not_raise
-
-Or, if you're supporting Python 3.3+ you can use:
-
-.. code-block:: python
-
-    from contextlib import ExitStack as does_not_raise
-
-Or, if desired, you can ``pip install contextlib2`` and use:
-
-.. code-block:: python
+        with expectation as e:
+            assert (6 / example_input) == e
 
-    from contextlib2 import nullcontext as does_not_raise
+In the example above, the first three test cases should run without any
+exceptions, while the fourth should raise a ``ZeroDivisionError`` exception,
+which is expected by pytest.
diff --git a/doc/en/example/pythoncollection.py b/doc/en/example/pythoncollection.py
index 8742526a191..7595ee02ca4 100644
--- a/doc/en/example/pythoncollection.py
+++ b/doc/en/example/pythoncollection.py
@@ -1,5 +1,6 @@
 # run this with $ pytest --collect-only test_collectonly.py
 #
+from __future__ import annotations
 
 
 def test_function():
diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst
index 8b7f05bb76d..6a3b143d580 100644
--- a/doc/en/example/pythoncollection.rst
+++ b/doc/en/example/pythoncollection.rst
@@ -147,13 +147,14 @@ The test collection would look like this:
 
     $ pytest --collect-only
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
-    rootdir: /home/sweet/project, configfile: pytest.ini
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
+    rootdir: /home/sweet/project
+    configfile: pytest.ini
     collected 2 items
 
-    <Module check_myapp.py>
-      <Class CheckMyApp>
+    <Dir pythoncollection.rst-207>
+      <Module check_myapp.py>
+        <Class CheckMyApp>
           <Function simple_check>
           <Function complex_check>
 
@@ -209,16 +210,18 @@ You can always peek at the collection tree without running tests like this:
 
     . $ pytest --collect-only pythoncollection.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
-    rootdir: /home/sweet/project, configfile: pytest.ini
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
+    rootdir: /home/sweet/project
+    configfile: pytest.ini
     collected 3 items
 
-    <Module CWD/pythoncollection.py>
-      <Function test_function>
-      <Class TestClass>
-          <Function test_method>
-          <Function test_anothermethod>
+    <Dir pythoncollection.rst-207>
+      <Dir CWD>
+        <Module pythoncollection.py>
+          <Function test_function>
+          <Class TestClass>
+            <Function test_method>
+            <Function test_anothermethod>
 
     ======================== 3 tests collected in 0.12s ========================
 
@@ -291,9 +294,9 @@ file will be left out:
 
     $ pytest --collect-only
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
-    rootdir: /home/sweet/project, configfile: pytest.ini
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
+    rootdir: /home/sweet/project
+    configfile: pytest.ini
     collected 0 items
 
     ======================= no tests collected in 0.12s ========================
@@ -322,3 +325,30 @@ with ``Test`` by setting a boolean ``__test__`` attribute to ``False``.
     # Will not be discovered as a test
     class TestClass:
         __test__ = False
+
+.. note::
+
+   If you are working with abstract test classes and want to avoid manually setting
+   the ``__test__`` attribute for subclasses, you can use a mixin class to handle
+   this automatically. For example:
+
+   .. code-block:: python
+
+       # Mixin to handle abstract test classes
+       class NotATest:
+           def __init_subclass__(cls):
+               cls.__test__ = NotATest not in cls.__bases__
+
+
+       # Abstract test class
+       class AbstractTest(NotATest):
+           pass
+
+
+       # Subclass that will be collected as a test
+       class RealTest(AbstractTest):
+           def test_example(self):
+               assert 1 + 1 == 2
+
+   This approach ensures that subclasses of abstract test classes are automatically
+   collected without needing to explicitly set the ``__test__`` attribute.
diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst
index ff814bd754a..5e48815bbc9 100644
--- a/doc/en/example/reportingdemo.rst
+++ b/doc/en/example/reportingdemo.rst
@@ -9,8 +9,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
 
     assertion $ pytest failure_demo.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project/assertion
     collected 44 items
 
@@ -26,7 +25,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       assert param1 * 2 < param2
     E       assert (3 * 2) < 6
 
-    failure_demo.py:19: AssertionError
+    failure_demo.py:21: AssertionError
     _________________________ TestFailing.test_simple __________________________
 
     self = <failure_demo.TestFailing object at 0xdeadbeef0001>
@@ -43,7 +42,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E        +  where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef0002>()
     E        +  and   43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef0003>()
 
-    failure_demo.py:30: AssertionError
+    failure_demo.py:32: AssertionError
     ____________________ TestFailing.test_simple_multiline _____________________
 
     self = <failure_demo.TestFailing object at 0xdeadbeef0004>
@@ -51,7 +50,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
         def test_simple_multiline(self):
     >       otherfunc_multi(42, 6 * 9)
 
-    failure_demo.py:33:
+    failure_demo.py:35:
     _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
     a = 42, b = 54
@@ -60,7 +59,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       assert a == b
     E       assert 42 == 54
 
-    failure_demo.py:14: AssertionError
+    failure_demo.py:16: AssertionError
     ___________________________ TestFailing.test_not ___________________________
 
     self = <failure_demo.TestFailing object at 0xdeadbeef0005>
@@ -73,7 +72,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E       assert not 42
     E        +  where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef0006>()
 
-    failure_demo.py:39: AssertionError
+    failure_demo.py:41: AssertionError
     _________________ TestSpecialisedExplanations.test_eq_text _________________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0007>
@@ -81,10 +80,11 @@ Here is a nice run of several failures and how ``pytest`` presents things:
         def test_eq_text(self):
     >       assert "spam" == "eggs"
     E       AssertionError: assert 'spam' == 'eggs'
+    E
     E         - eggs
     E         + spam
 
-    failure_demo.py:44: AssertionError
+    failure_demo.py:46: AssertionError
     _____________ TestSpecialisedExplanations.test_eq_similar_text _____________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0008>
@@ -92,12 +92,13 @@ Here is a nice run of several failures and how ``pytest`` presents things:
         def test_eq_similar_text(self):
     >       assert "foo 1 bar" == "foo 2 bar"
     E       AssertionError: assert 'foo 1 bar' == 'foo 2 bar'
+    E
     E         - foo 2 bar
     E         ?     ^
     E         + foo 1 bar
     E         ?     ^
 
-    failure_demo.py:47: AssertionError
+    failure_demo.py:49: AssertionError
     ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0009>
@@ -105,12 +106,13 @@ Here is a nice run of several failures and how ``pytest`` presents things:
         def test_eq_multiline_text(self):
     >       assert "foo\nspam\nbar" == "foo\neggs\nbar"
     E       AssertionError: assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
+    E
     E           foo
     E         - eggs
     E         + spam
     E           bar
 
-    failure_demo.py:50: AssertionError
+    failure_demo.py:52: AssertionError
     ______________ TestSpecialisedExplanations.test_eq_long_text _______________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000a>
@@ -120,6 +122,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
             b = "1" * 100 + "b" + "2" * 100
     >       assert a == b
     E       AssertionError: assert '111111111111...2222222222222' == '111111111111...2222222222222'
+    E
     E         Skipping 90 identical leading characters in diff, use -v to show
     E         Skipping 91 identical trailing characters in diff, use -v to show
     E         - 1111111111b222222222
@@ -127,7 +130,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E         + 1111111111a222222222
     E         ?           ^
 
-    failure_demo.py:55: AssertionError
+    failure_demo.py:57: AssertionError
     _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000b>
@@ -137,17 +140,17 @@ Here is a nice run of several failures and how ``pytest`` presents things:
             b = "1\n" * 100 + "b" + "2\n" * 100
     >       assert a == b
     E       AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n...n2\n2\n2\n2\n'
+    E
     E         Skipping 190 identical leading characters in diff, use -v to show
     E         Skipping 191 identical trailing characters in diff, use -v to show
     E           1
     E           1
     E           1
-    E           1
     E           1...
     E
     E         ...Full output truncated (7 lines hidden), use '-vv' to show
 
-    failure_demo.py:60: AssertionError
+    failure_demo.py:62: AssertionError
     _________________ TestSpecialisedExplanations.test_eq_list _________________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000c>
@@ -155,10 +158,11 @@ Here is a nice run of several failures and how ``pytest`` presents things:
         def test_eq_list(self):
     >       assert [0, 1, 2] == [0, 1, 3]
     E       assert [0, 1, 2] == [0, 1, 3]
+    E
     E         At index 2 diff: 2 != 3
-    E         Use -v to get the full diff
+    E         Use -v to get more diff
 
-    failure_demo.py:63: AssertionError
+    failure_demo.py:65: AssertionError
     ______________ TestSpecialisedExplanations.test_eq_list_long _______________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000d>
@@ -168,10 +172,11 @@ Here is a nice run of several failures and how ``pytest`` presents things:
             b = [0] * 100 + [2] + [3] * 100
     >       assert a == b
     E       assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...]
+    E
     E         At index 100 diff: 1 != 2
-    E         Use -v to get the full diff
+    E         Use -v to get more diff
 
-    failure_demo.py:68: AssertionError
+    failure_demo.py:70: AssertionError
     _________________ TestSpecialisedExplanations.test_eq_dict _________________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000e>
@@ -179,35 +184,35 @@ Here is a nice run of several failures and how ``pytest`` presents things:
         def test_eq_dict(self):
     >       assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0}
     E       AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
+    E
     E         Omitting 1 identical items, use -vv to show
     E         Differing items:
     E         {'b': 1} != {'b': 2}
     E         Left contains 1 more item:
     E         {'c': 0}
     E         Right contains 1 more item:
-    E         {'d': 0}...
-    E
-    E         ...Full output truncated (2 lines hidden), use '-vv' to show
+    E         {'d': 0}
+    E         Use -v to get more diff
 
-    failure_demo.py:71: AssertionError
+    failure_demo.py:73: AssertionError
     _________________ TestSpecialisedExplanations.test_eq_set __________________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000f>
 
         def test_eq_set(self):
     >       assert {0, 10, 11, 12} == {0, 20, 21}
-    E       AssertionError: assert {0, 10, 11, 12} == {0, 20, 21}
+    E       assert {0, 10, 11, 12} == {0, 20, 21}
+    E
     E         Extra items in the left set:
     E         10
     E         11
     E         12
     E         Extra items in the right set:
     E         20
-    E         21...
-    E
-    E         ...Full output truncated (2 lines hidden), use '-vv' to show
+    E         21
+    E         Use -v to get more diff
 
-    failure_demo.py:74: AssertionError
+    failure_demo.py:76: AssertionError
     _____________ TestSpecialisedExplanations.test_eq_longer_list ______________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0010>
@@ -215,10 +220,11 @@ Here is a nice run of several failures and how ``pytest`` presents things:
         def test_eq_longer_list(self):
     >       assert [1, 2] == [1, 2, 3]
     E       assert [1, 2] == [1, 2, 3]
+    E
     E         Right contains one more item: 3
-    E         Use -v to get the full diff
+    E         Use -v to get more diff
 
-    failure_demo.py:77: AssertionError
+    failure_demo.py:79: AssertionError
     _________________ TestSpecialisedExplanations.test_in_list _________________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0011>
@@ -227,7 +233,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       assert 1 in [0, 2, 3, 4, 5]
     E       assert 1 in [0, 2, 3, 4, 5]
 
-    failure_demo.py:80: AssertionError
+    failure_demo.py:82: AssertionError
     __________ TestSpecialisedExplanations.test_not_in_text_multiline __________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0012>
@@ -236,17 +242,17 @@ Here is a nice run of several failures and how ``pytest`` presents things:
             text = "some multiline\ntext\nwhich\nincludes foo\nand a\ntail"
     >       assert "foo" not in text
     E       AssertionError: assert 'foo' not in 'some multil...nand a\ntail'
+    E
     E         'foo' is contained here:
     E           some multiline
     E           text
     E           which
     E           includes foo
     E         ?          +++
-    E           and a...
-    E
-    E         ...Full output truncated (2 lines hidden), use '-vv' to show
+    E           and a
+    E           tail
 
-    failure_demo.py:84: AssertionError
+    failure_demo.py:86: AssertionError
     ___________ TestSpecialisedExplanations.test_not_in_text_single ____________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0013>
@@ -255,11 +261,12 @@ Here is a nice run of several failures and how ``pytest`` presents things:
             text = "single foo line"
     >       assert "foo" not in text
     E       AssertionError: assert 'foo' not in 'single foo line'
+    E
     E         'foo' is contained here:
     E           single foo line
     E         ?        +++
 
-    failure_demo.py:88: AssertionError
+    failure_demo.py:90: AssertionError
     _________ TestSpecialisedExplanations.test_not_in_text_single_long _________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0014>
@@ -268,11 +275,12 @@ Here is a nice run of several failures and how ``pytest`` presents things:
             text = "head " * 50 + "foo " + "tail " * 20
     >       assert "foo" not in text
     E       AssertionError: assert 'foo' not in 'head head h...l tail tail '
+    E
     E         'foo' is contained here:
     E           head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
     E         ?           +++
 
-    failure_demo.py:92: AssertionError
+    failure_demo.py:94: AssertionError
     ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0015>
@@ -281,11 +289,12 @@ Here is a nice run of several failures and how ``pytest`` presents things:
             text = "head " * 50 + "f" * 70 + "tail " * 20
     >       assert "f" * 70 not in text
     E       AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head h...l tail tail '
+    E
     E         'ffffffffffffffffff...fffffffffffffffffff' is contained here:
     E           head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
     E         ?           ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
-    failure_demo.py:96: AssertionError
+    failure_demo.py:98: AssertionError
     ______________ TestSpecialisedExplanations.test_eq_dataclass _______________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0016>
@@ -308,11 +317,11 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E         ['b']
     E
     E         Drill down into differing attribute b:
-    E           b: 'b' != 'c'...
-    E
-    E         ...Full output truncated (3 lines hidden), use '-vv' to show
+    E           b: 'b' != 'c'
+    E           - c
+    E           + b
 
-    failure_demo.py:108: AssertionError
+    failure_demo.py:110: AssertionError
     ________________ TestSpecialisedExplanations.test_eq_attrs _________________
 
     self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0017>
@@ -335,11 +344,11 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E         ['b']
     E
     E         Drill down into differing attribute b:
-    E           b: 'b' != 'c'...
-    E
-    E         ...Full output truncated (3 lines hidden), use '-vv' to show
+    E           b: 'b' != 'c'
+    E           - c
+    E           + b
 
-    failure_demo.py:120: AssertionError
+    failure_demo.py:122: AssertionError
     ______________________________ test_attribute ______________________________
 
         def test_attribute():
@@ -351,7 +360,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E       assert 1 == 2
     E        +  where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef0018>.b
 
-    failure_demo.py:128: AssertionError
+    failure_demo.py:130: AssertionError
     _________________________ test_attribute_instance __________________________
 
         def test_attribute_instance():
@@ -363,7 +372,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E        +  where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef0019>.b
     E        +    where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef0019> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
 
-    failure_demo.py:135: AssertionError
+    failure_demo.py:137: AssertionError
     __________________________ test_attribute_failure __________________________
 
         def test_attribute_failure():
@@ -376,7 +385,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
             i = Foo()
     >       assert i.b == 2
 
-    failure_demo.py:146:
+    failure_demo.py:148:
     _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
     self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef001a>
@@ -385,7 +394,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       raise Exception("Failed to get attrib")
     E       Exception: Failed to get attrib
 
-    failure_demo.py:141: Exception
+    failure_demo.py:143: Exception
     _________________________ test_attribute_multiple __________________________
 
         def test_attribute_multiple():
@@ -402,7 +411,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E        +  and   2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef001c>.b
     E        +    where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef001c> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
 
-    failure_demo.py:156: AssertionError
+    failure_demo.py:158: AssertionError
     __________________________ TestRaises.test_raises __________________________
 
     self = <failure_demo.TestRaises object at 0xdeadbeef001d>
@@ -412,7 +421,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       raises(TypeError, int, s)
     E       ValueError: invalid literal for int() with base 10: 'qwe'
 
-    failure_demo.py:166: ValueError
+    failure_demo.py:168: ValueError
     ______________________ TestRaises.test_raises_doesnt _______________________
 
     self = <failure_demo.TestRaises object at 0xdeadbeef001e>
@@ -421,7 +430,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       raises(OSError, int, "3")
     E       Failed: DID NOT RAISE <class 'OSError'>
 
-    failure_demo.py:169: Failed
+    failure_demo.py:171: Failed
     __________________________ TestRaises.test_raise ___________________________
 
     self = <failure_demo.TestRaises object at 0xdeadbeef001f>
@@ -430,16 +439,16 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       raise ValueError("demo error")
     E       ValueError: demo error
 
-    failure_demo.py:172: ValueError
+    failure_demo.py:174: ValueError
     ________________________ TestRaises.test_tupleerror ________________________
 
     self = <failure_demo.TestRaises object at 0xdeadbeef0020>
 
         def test_tupleerror(self):
-    >       a, b = [1]  # NOQA
+    >       a, b = [1]  # noqa: F841
     E       ValueError: not enough values to unpack (expected 2, got 1)
 
-    failure_demo.py:175: ValueError
+    failure_demo.py:177: ValueError
     ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
 
     self = <failure_demo.TestRaises object at 0xdeadbeef0021>
@@ -450,7 +459,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       a, b = items.pop()
     E       TypeError: cannot unpack non-iterable int object
 
-    failure_demo.py:180: TypeError
+    failure_demo.py:182: TypeError
     --------------------------- Captured stdout call ---------------------------
     items is [1, 2, 3]
     ________________________ TestRaises.test_some_error ________________________
@@ -458,10 +467,10 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     self = <failure_demo.TestRaises object at 0xdeadbeef0022>
 
         def test_some_error(self):
-    >       if namenotexi:  # NOQA
+    >       if namenotexi:  # noqa: F821
     E       NameError: name 'namenotexi' is not defined
 
-    failure_demo.py:183: NameError
+    failure_demo.py:185: NameError
     ____________________ test_dynamic_compile_shows_nicely _____________________
 
         def test_dynamic_compile_shows_nicely():
@@ -477,7 +486,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
             sys.modules[name] = module
     >       module.foo()
 
-    failure_demo.py:202:
+    failure_demo.py:204:
     _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
     >   ???
@@ -497,9 +506,9 @@ Here is a nice run of several failures and how ``pytest`` presents things:
 
     >       somefunc(f(), g())
 
-    failure_demo.py:213:
+    failure_demo.py:215:
     _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
-    failure_demo.py:10: in somefunc
+    failure_demo.py:12: in somefunc
         otherfunc(x, y)
     _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 
@@ -509,7 +518,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       assert a == b
     E       assert 44 == 43
 
-    failure_demo.py:6: AssertionError
+    failure_demo.py:8: AssertionError
     ___________________ TestMoreErrors.test_z1_unpack_error ____________________
 
     self = <failure_demo.TestMoreErrors object at 0xdeadbeef0024>
@@ -519,7 +528,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       a, b = items
     E       ValueError: not enough values to unpack (expected 2, got 0)
 
-    failure_demo.py:217: ValueError
+    failure_demo.py:219: ValueError
     ____________________ TestMoreErrors.test_z2_type_error _____________________
 
     self = <failure_demo.TestMoreErrors object at 0xdeadbeef0025>
@@ -529,7 +538,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >       a, b = items
     E       TypeError: cannot unpack non-iterable int object
 
-    failure_demo.py:221: TypeError
+    failure_demo.py:223: TypeError
     ______________________ TestMoreErrors.test_startswith ______________________
 
     self = <failure_demo.TestMoreErrors object at 0xdeadbeef0026>
@@ -542,7 +551,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E        +  where False = <built-in method startswith of str object at 0xdeadbeef0027>('456')
     E        +    where <built-in method startswith of str object at 0xdeadbeef0027> = '123'.startswith
 
-    failure_demo.py:226: AssertionError
+    failure_demo.py:228: AssertionError
     __________________ TestMoreErrors.test_startswith_nested ___________________
 
     self = <failure_demo.TestMoreErrors object at 0xdeadbeef0028>
@@ -559,12 +568,12 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E        +  where False = <built-in method startswith of str object at 0xdeadbeef0027>('456')
     E        +    where <built-in method startswith of str object at 0xdeadbeef0027> = '123'.startswith
     E        +      where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef0029>()
-    E        +    and   '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef002a>()
+    E        +    and   '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef0003>()
 
-    failure_demo.py:235: AssertionError
+    failure_demo.py:237: AssertionError
     _____________________ TestMoreErrors.test_global_func ______________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002a>
 
         def test_global_func(self):
     >       assert isinstance(globf(42), float)
@@ -572,31 +581,31 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E        +  where False = isinstance(43, float)
     E        +    where 43 = globf(42)
 
-    failure_demo.py:238: AssertionError
+    failure_demo.py:240: AssertionError
     _______________________ TestMoreErrors.test_instance _______________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>
 
         def test_instance(self):
             self.x = 6 * 7
     >       assert self.x != 42
     E       assert 42 != 42
-    E        +  where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>.x
+    E        +  where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>.x
 
-    failure_demo.py:242: AssertionError
+    failure_demo.py:244: AssertionError
     _______________________ TestMoreErrors.test_compare ________________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002d>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>
 
         def test_compare(self):
     >       assert globf(10) < 5
     E       assert 11 < 5
     E        +  where 11 = globf(10)
 
-    failure_demo.py:245: AssertionError
+    failure_demo.py:247: AssertionError
     _____________________ TestMoreErrors.test_try_finally ______________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002e>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002d>
 
         def test_try_finally(self):
             x = 1
@@ -604,10 +613,10 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     >           assert x == 0
     E           assert 1 == 0
 
-    failure_demo.py:250: AssertionError
+    failure_demo.py:252: AssertionError
     ___________________ TestCustomAssertMsg.test_single_line ___________________
 
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002f>
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002e>
 
         def test_single_line(self):
             class A:
@@ -619,10 +628,10 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E       assert 1 == 2
     E        +  where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
 
-    failure_demo.py:261: AssertionError
+    failure_demo.py:263: AssertionError
     ____________________ TestCustomAssertMsg.test_multiline ____________________
 
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0030>
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002f>
 
         def test_multiline(self):
             class A:
@@ -638,10 +647,10 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E       assert 1 == 2
     E        +  where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
 
-    failure_demo.py:268: AssertionError
+    failure_demo.py:270: AssertionError
     ___________________ TestCustomAssertMsg.test_custom_repr ___________________
 
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0031>
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0030>
 
         def test_custom_repr(self):
             class JSON:
@@ -660,7 +669,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E       assert 1 == 2
     E        +  where 1 = This is JSON\n{\n  'foo': 'bar'\n}.a
 
-    failure_demo.py:281: AssertionError
+    failure_demo.py:283: AssertionError
     ========================= short test summary info ==========================
     FAILED failure_demo.py::test_generative[3-6] - assert (3 * 2) < 6
     FAILED failure_demo.py::TestFailing::test_simple - assert 42 == 43
@@ -674,7 +683,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list - asser...
     FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list_long - ...
     FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_dict - Asser...
-    FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - Assert...
+    FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - assert...
     FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_longer_list
     FAILED failure_demo.py::TestSpecialisedExplanations::test_in_list - asser...
     FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_multiline
diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst
index 3b9963f09d6..69d973e51d0 100644
--- a/doc/en/example/simple.rst
+++ b/doc/en/example/simple.rst
@@ -164,11 +164,11 @@ Now we'll get feedback on a bad argument:
 
     $ pytest -q --cmdopt=type3
     ERROR: usage: pytest [options] [file_or_dir] [file_or_dir] [...]
-    pytest: error: argument --cmdopt: invalid choice: 'type3' (choose from 'type1', 'type2')
+    pytest: error: argument --cmdopt: invalid choice: 'type3' (choose from type1, type2)
 
 
 If you need to provide more detailed error messages, you can use the
-``type`` parameter and raise ``pytest.UsageError``:
+``type`` parameter and raise :exc:`pytest.UsageError`:
 
 .. code-block:: python
 
@@ -212,7 +212,7 @@ the command line arguments before they get processed:
 
 .. code-block:: python
 
-    # setuptools plugin
+    # installable external plugin
     import sys
 
 
@@ -232,8 +232,7 @@ directory with the above conftest.py:
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 0 items
 
@@ -297,8 +296,7 @@ and when running it will see a skipped "slow" test:
 
     $ pytest -rs    # "-rs" means report details on the little 's'
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
 
@@ -314,8 +312,7 @@ Or run it including the ``slow`` marked test:
 
     $ pytest --runslow
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
 
@@ -345,7 +342,7 @@ Example:
     def checkconfig(x):
         __tracebackhide__ = True
         if not hasattr(x, "config"):
-            pytest.fail("not configured: {}".format(x))
+            pytest.fail(f"not configured: {x}")
 
 
     def test_something():
@@ -379,6 +376,7 @@ this to make sure unexpected exception types aren't hidden:
 .. code-block:: python
 
     import operator
+
     import pytest
 
 
@@ -389,7 +387,7 @@ this to make sure unexpected exception types aren't hidden:
     def checkconfig(x):
         __tracebackhide__ = operator.methodcaller("errisinstance", ConfigException)
         if not hasattr(x, "config"):
-            raise ConfigException("not configured: {}".format(x))
+            raise ConfigException(f"not configured: {x}")
 
 
     def test_something():
@@ -407,35 +405,20 @@ Detect if running from within a pytest run
 Usually it is a bad idea to make application code
 behave differently if called from a test.  But if you
 absolutely must find out if your application code is
-running from a test you can do something like this:
+running from a test you can do this:
 
 .. code-block:: python
 
-    # content of your_module.py
-
-
-    _called_from_test = False
-
-.. code-block:: python
-
-    # content of conftest.py
-
-
-    def pytest_configure(config):
-        your_module._called_from_test = True
+    import os
 
-and then check for the ``your_module._called_from_test`` flag:
 
-.. code-block:: python
-
-    if your_module._called_from_test:
-        # called from within a test run
+    if os.environ.get("PYTEST_VERSION") is not None:
+        # Things you want to to do if your code is called by pytest.
         ...
     else:
-        # called "normally"
+        # Things you want to to do if your code is not called by pytest.
         ...
 
-accordingly in your application.
 
 Adding info to test report header
 --------------------------------------------------------------
@@ -458,8 +441,7 @@ which will add the string to the test header accordingly:
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     project deps: mylib-1.1
     rootdir: /home/sweet/project
     collected 0 items
@@ -478,7 +460,7 @@ display more information if applicable:
 
 
     def pytest_report_header(config):
-        if config.getoption("verbose") > 0:
+        if config.get_verbosity() > 0:
             return ["info1: did you know that ...", "did you?"]
 
 which will add info only when run with "--v":
@@ -487,7 +469,7 @@ which will add info only when run with "--v":
 
     $ pytest -v
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     info1: did you know that ...
     did you?
@@ -502,8 +484,7 @@ and nothing when run plainly:
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 0 items
 
@@ -542,8 +523,7 @@ Now we can profile which test functions execute the slowest:
 
     $ pytest --durations=3
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 3 items
 
@@ -571,6 +551,7 @@ an ``incremental`` marker which is to be used on classes:
     # content of conftest.py
 
     from typing import Dict, Tuple
+
     import pytest
 
     # store history of failures per test class name and per index in parametrize (if parametrize used)
@@ -614,7 +595,7 @@ an ``incremental`` marker which is to be used on classes:
                 test_name = _test_failed_incremental[cls_name].get(parametrize_index, None)
                 # if name found, test has failed for the combination of class name & test name
                 if test_name is not None:
-                    pytest.xfail("previous test failed ({})".format(test_name))
+                    pytest.xfail(f"previous test failed ({test_name})")
 
 
 These two hook implementations work together to abort incremental-marked
@@ -648,8 +629,7 @@ If we run this:
 
     $ pytest -rx
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 4 items
 
@@ -666,8 +646,7 @@ If we run this:
 
     test_step.py:11: AssertionError
     ========================= short test summary info ==========================
-    XFAIL test_step.py::TestUserHandling::test_deletion
-      reason: previous test failed (test_modification)
+    XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification)
     ================== 1 failed, 2 passed, 1 xfailed in 0.12s ==================
 
 We'll see that ``test_deletion`` was not executed because ``test_modification``
@@ -697,7 +676,7 @@ Here is an example for making a ``db`` fixture available in a directory:
         pass
 
 
-    @pytest.fixture(scope="session")
+    @pytest.fixture(scope="package")
     def db():
         return DB()
 
@@ -732,15 +711,14 @@ We can run this:
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 7 items
 
-    test_step.py .Fx.                                                    [ 57%]
-    a/test_db.py F                                                       [ 71%]
-    a/test_db2.py F                                                      [ 85%]
-    b/test_error.py E                                                    [100%]
+    a/test_db.py F                                                       [ 14%]
+    a/test_db2.py F                                                      [ 28%]
+    b/test_error.py E                                                    [ 42%]
+    test_step.py .Fx.                                                    [100%]
 
     ================================== ERRORS ==================================
     _______________________ ERROR at setup of test_root ________________________
@@ -752,39 +730,39 @@ We can run this:
 
     /home/sweet/project/b/test_error.py:1
     ================================= FAILURES =================================
-    ____________________ TestUserHandling.test_modification ____________________
-
-    self = <test_step.TestUserHandling object at 0xdeadbeef0002>
-
-        def test_modification(self):
-    >       assert 0
-    E       assert 0
-
-    test_step.py:11: AssertionError
     _________________________________ test_a1 __________________________________
 
-    db = <conftest.DB object at 0xdeadbeef0003>
+    db = <conftest.DB object at 0xdeadbeef0002>
 
         def test_a1(db):
     >       assert 0, db  # to show value
-    E       AssertionError: <conftest.DB object at 0xdeadbeef0003>
+    E       AssertionError: <conftest.DB object at 0xdeadbeef0002>
     E       assert 0
 
     a/test_db.py:2: AssertionError
     _________________________________ test_a2 __________________________________
 
-    db = <conftest.DB object at 0xdeadbeef0003>
+    db = <conftest.DB object at 0xdeadbeef0002>
 
         def test_a2(db):
     >       assert 0, db  # to show value
-    E       AssertionError: <conftest.DB object at 0xdeadbeef0003>
+    E       AssertionError: <conftest.DB object at 0xdeadbeef0002>
     E       assert 0
 
     a/test_db2.py:2: AssertionError
+    ____________________ TestUserHandling.test_modification ____________________
+
+    self = <test_step.TestUserHandling object at 0xdeadbeef0003>
+
+        def test_modification(self):
+    >       assert 0
+    E       assert 0
+
+    test_step.py:11: AssertionError
     ========================= short test summary info ==========================
-    FAILED test_step.py::TestUserHandling::test_modification - assert 0
     FAILED a/test_db.py::test_a1 - AssertionError: <conftest.DB object at 0x7...
     FAILED a/test_db2.py::test_a2 - AssertionError: <conftest.DB object at 0x...
+    FAILED test_step.py::TestUserHandling::test_modification - assert 0
     ERROR b/test_error.py::test_root
     ============= 3 failed, 2 passed, 1 xfailed, 1 error in 0.12s ==============
 
@@ -810,20 +788,20 @@ case we just write some information out to a ``failures`` file:
 
     # content of conftest.py
 
-    import pytest
     import os.path
 
+    import pytest
+
 
-    @pytest.hookimpl(tryfirst=True, hookwrapper=True)
+    @pytest.hookimpl(wrapper=True, tryfirst=True)
     def pytest_runtest_makereport(item, call):
         # execute all other hooks to obtain the report object
-        outcome = yield
-        rep = outcome.get_result()
+        rep = yield
 
         # we only look at actual failing test calls, not setup/teardown
         if rep.when == "call" and rep.failed:
             mode = "a" if os.path.exists("failures") else "w"
-            with open("failures", mode) as f:
+            with open("failures", mode, encoding="utf-8") as f:
                 # let's also access a fixture for the fun of it
                 if "tmp_path" in item.fixturenames:
                     extra = " ({})".format(item.funcargs["tmp_path"])
@@ -832,6 +810,8 @@ case we just write some information out to a ``failures`` file:
 
                 f.write(rep.nodeid + extra + "\n")
 
+        return rep
+
 
 if you then have failing tests:
 
@@ -851,8 +831,7 @@ and run them:
 
     $ pytest test_module.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
 
@@ -899,20 +878,23 @@ here is a little example implemented via a local plugin:
 .. code-block:: python
 
     # content of conftest.py
-
+    from typing import Dict
     import pytest
+    from pytest import StashKey, CollectReport
 
+    phase_report_key = StashKey[Dict[str, CollectReport]]()
 
-    @pytest.hookimpl(tryfirst=True, hookwrapper=True)
+
+    @pytest.hookimpl(wrapper=True, tryfirst=True)
     def pytest_runtest_makereport(item, call):
         # execute all other hooks to obtain the report object
-        outcome = yield
-        rep = outcome.get_result()
+        rep = yield
 
-        # set a report attribute for each phase of a call, which can
+        # store test results for each phase of a call, which can
         # be "setup", "call", "teardown"
+        item.stash.setdefault(phase_report_key, {})[rep.when] = rep
 
-        setattr(item, "rep_" + rep.when, rep)
+        return rep
 
 
     @pytest.fixture
@@ -920,11 +902,13 @@ here is a little example implemented via a local plugin:
         yield
         # request.node is an "item" because we use the default
         # "function" scope
-        if request.node.rep_setup.failed:
-            print("setting up a test failed!", request.node.nodeid)
-        elif request.node.rep_setup.passed:
-            if request.node.rep_call.failed:
-                print("executing test failed", request.node.nodeid)
+        report = request.node.stash[phase_report_key]
+        if report["setup"].failed:
+            print("setting up a test failed", request.node.nodeid)
+        elif report["setup"].skipped:
+            print("setting up a test skipped", request.node.nodeid)
+        elif ("call" not in report) or report["call"].failed:
+            print("executing test failed or skipped", request.node.nodeid)
 
 
 if you then have failing tests:
@@ -958,13 +942,12 @@ and run it:
 
     $ pytest -s test_module.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 3 items
 
-    test_module.py Esetting up a test failed! test_module.py::test_setup_fails
-    Fexecuting test failed test_module.py::test_call_fails
+    test_module.py Esetting up a test failed or skipped test_module.py::test_setup_fails
+    Fexecuting test failed or skipped test_module.py::test_call_fails
     F
 
     ================================== ERRORS ==================================
@@ -1067,8 +1050,8 @@ Instead of freezing the pytest runner as a separate executable, you can make
 your frozen program work as the pytest runner by some clever
 argument handling during program startup. This allows you to
 have a single executable, which is usually more convenient.
-Please note that the mechanism for plugin discovery used by pytest
-(setuptools entry points) doesn't work with frozen executables so pytest
+Please note that the mechanism for plugin discovery used by pytest (:ref:`entry
+points <pip-installable plugins>`) doesn't work with frozen executables so pytest
 can't find any third party plugins automatically. To include third party plugins
 like ``pytest-timeout`` they must be imported explicitly and passed on to pytest.main.
 
@@ -1076,6 +1059,7 @@ like ``pytest-timeout`` they must be imported explicitly and passed on to pytest
 
     # contents of app_main.py
     import sys
+
     import pytest_timeout  # Third party plugin
 
     if len(sys.argv) > 1 and sys.argv[1] == "--pytest":
@@ -1093,4 +1077,4 @@ application with standard ``pytest`` command-line options:
 
 .. code-block:: bash
 
-    ./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/
+    ./app_main --pytest --verbose --tb=long --junit=xml=results.xml test-suite/
diff --git a/doc/en/example/xfail_demo.py b/doc/en/example/xfail_demo.py
index 01e6da1ad2e..4999e15f238 100644
--- a/doc/en/example/xfail_demo.py
+++ b/doc/en/example/xfail_demo.py
@@ -1,5 +1,8 @@
+from __future__ import annotations
+
 import pytest
 
+
 xfail = pytest.mark.xfail
 
 
diff --git a/doc/en/explanation/anatomy.rst b/doc/en/explanation/anatomy.rst
index e86dd74251e..93d3400dae2 100644
--- a/doc/en/explanation/anatomy.rst
+++ b/doc/en/explanation/anatomy.rst
@@ -34,7 +34,7 @@ a function/method call.
 
 **Assert** is where we look at that resulting state and check if it looks how
 we'd expect after the dust has settled. It's where we gather evidence to say the
-behavior does or does not aligns with what we expect. The ``assert`` in our test
+behavior does or does not align with what we expect. The ``assert`` in our test
 is where we take that measurement/observation and apply our judgement to it. If
 something should be green, we'd say ``assert thing == "green"``.
 
diff --git a/doc/en/explanation/ci.rst b/doc/en/explanation/ci.rst
new file mode 100644
index 00000000000..45fe658d14f
--- /dev/null
+++ b/doc/en/explanation/ci.rst
@@ -0,0 +1,70 @@
+.. _`ci-pipelines`:
+
+CI Pipelines
+============
+
+Rationale
+---------
+
+The goal of testing in a CI pipeline is different from testing locally. Indeed,
+you can quickly edit some code and run your tests again on your computer, but
+it is not possible with CI pipeline. They run on a separate server and are
+triggered by specific actions.
+
+From that observation, pytest can detect when it is in a CI environment and
+adapt some of its behaviours.
+
+How CI is detected
+------------------
+
+Pytest knows it is in a CI environment when either one of these environment variables are set,
+regardless of their value:
+
+* `CI`: used by many CI systems.
+* `BUILD_NUMBER`: used by Jenkins.
+
+Effects on CI
+-------------
+
+For now, the effects on pytest of being in a CI environment are limited.
+
+When a CI environment is detected, the output of the short test summary info is no longer truncated to the terminal size i.e. the entire message will be shown.
+
+  .. code-block:: python
+
+        # content of test_ci.py
+        import pytest
+
+
+        def test_db_initialized():
+            pytest.fail(
+                "deliberately failing for demo purpose, Lorem ipsum dolor sit amet, "
+                "consectetur adipiscing elit. Cras facilisis, massa in suscipit "
+                "dignissim, mauris lacus molestie nisi, quis varius metus nulla ut ipsum."
+            )
+
+
+Running this locally, without any extra options, will output:
+
+  .. code-block:: pytest
+
+     $ pytest test_ci.py
+     ...
+     ========================= short test summary info ==========================
+     FAILED test_backends.py::test_db_initialized[d2] - Failed: deliberately f...
+
+*(Note the truncated text)*
+
+
+While running this on CI will output:
+
+  .. code-block:: pytest
+
+     $ export CI=true
+     $ pytest test_ci.py
+     ...
+     ========================= short test summary info ==========================
+     FAILED test_backends.py::test_db_initialized[d2] - Failed: deliberately failing
+     for demo purpose, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras
+     facilisis, massa in suscipit dignissim, mauris lacus molestie nisi, quis varius
+     metus nulla ut ipsum.
diff --git a/doc/en/explanation/fixtures.rst b/doc/en/explanation/fixtures.rst
index 194e576493e..53d4796c825 100644
--- a/doc/en/explanation/fixtures.rst
+++ b/doc/en/explanation/fixtures.rst
@@ -75,7 +75,7 @@ style of setup/teardown functions:
 
 * fixture management scales from simple unit to complex
   functional testing, allowing to parametrize fixtures and tests according
-  to configuration and component options, or to re-use fixtures
+  to configuration and component options, or to reuse fixtures
   across function, class, module or whole test session scopes.
 
 * teardown logic can be easily, and safely managed, no matter how many fixtures
@@ -85,7 +85,7 @@ style of setup/teardown functions:
 In addition, pytest continues to support :ref:`xunitsetup`.  You can mix
 both styles, moving incrementally from classic to new style, as you
 prefer.  You can also start out from existing :ref:`unittest.TestCase
-style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
+style <unittest.TestCase>`.
 
 
 
@@ -162,7 +162,7 @@ A note about fixture cleanup
 ----------------------------
 
 pytest does not do any special processing for :data:`SIGTERM <signal.SIGTERM>` and
-:data:`SIGQUIT <signal.SIGQUIT>` signals (:data:`SIGINT <signal.SIGINT>` is handled naturally
+``SIGQUIT`` signals (:data:`SIGINT <signal.SIGINT>` is handled naturally
 by the Python runtime via :class:`KeyboardInterrupt`), so fixtures that manage external resources which are important
 to be cleared when the Python process is terminated (by those signals) might leak resources.
 
diff --git a/doc/en/explanation/flaky.rst b/doc/en/explanation/flaky.rst
index 50121c7a761..8369e1d9311 100644
--- a/doc/en/explanation/flaky.rst
+++ b/doc/en/explanation/flaky.rst
@@ -18,7 +18,7 @@ System state
 
 Broadly speaking, a flaky test indicates that the test relies on some system state that is not being appropriately controlled - the test environment is not sufficiently isolated. Higher level tests are more likely to be flaky as they rely on more state.
 
-Flaky tests sometimes appear when a test suite is run in parallel (such as use of pytest-xdist). This can indicate a test is reliant on test ordering.
+Flaky tests sometimes appear when a test suite is run in parallel (such as use of `pytest-xdist`_). This can indicate a test is reliant on test ordering.
 
 -  Perhaps a different test is failing to clean up after itself and leaving behind data which causes the flaky test to fail.
 - The flaky test is reliant on data from a previous test that doesn't clean up after itself, and in parallel runs that previous test is not always present
@@ -30,9 +30,22 @@ Overly strict assertion
 
 Overly strict assertions can cause problems with floating point comparison as well as timing issues. :func:`pytest.approx` is useful here.
 
+Thread safety
+~~~~~~~~~~~~~
 
-Pytest features
-^^^^^^^^^^^^^^^
+pytest is single-threaded, executing its tests always in the same thread, sequentially, never spawning any threads itself.
+
+Even in case of plugins which run tests in parallel, for example `pytest-xdist`_, usually work by spawning multiple *processes* and running tests in batches, without using multiple threads.
+
+It is of course possible (and common) for tests and fixtures to spawn threads themselves as part of their testing workflow (for example, a fixture that starts a server thread in the background, or a test which executes production code that spawns threads), but some care must be taken:
+
+* Make sure to eventually wait on any spawned threads -- for example at the end of a test, or during the teardown of a fixture.
+* Avoid using primitives provided by pytest (:func:`pytest.warns`, :func:`pytest.raises`, etc) from multiple threads, as they are not thread-safe.
+
+If your test suite uses threads and your are seeing flaky test results, do not discount the possibility that the test is implicitly using global state in pytest itself.
+
+Related features
+^^^^^^^^^^^^^^^^
 
 Xfail strict
 ~~~~~~~~~~~~
@@ -52,10 +65,9 @@ Plugins
 
 Rerunning any failed tests can mitigate the negative effects of flaky tests by giving them additional chances to pass, so that the overall build does not fail. Several pytest plugins support this:
 
-* `flaky <https://github.com/box/flaky>`_
-* `pytest-flakefinder <https://github.com/dropbox/pytest-flakefinder>`_ - `blog post <https://blogs.dropbox.com/tech/2016/03/open-sourcing-pytest-tools/>`_
 * `pytest-rerunfailures <https://github.com/pytest-dev/pytest-rerunfailures>`_
 * `pytest-replay <https://github.com/ESSS/pytest-replay>`_: This plugin helps to reproduce locally crashes or flaky tests observed during CI runs.
+* `pytest-flakefinder <https://github.com/dropbox/pytest-flakefinder>`_ - `blog post <https://blogs.dropbox.com/tech/2016/03/open-sourcing-pytest-tools/>`_
 
 Plugins to deliberately randomize tests can help expose tests with state problems:
 
@@ -94,7 +106,7 @@ Mark Lapierre discusses the `Pros and Cons of Quarantined Tests <https://dev.to/
 CI tools that rerun on failure
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests <https://docs.microsoft.com/en-us/azure/devops/release-notes/2017/dec-11-vsts#identify-flaky-tests>`_ and rerun failed tests.
+Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests <https://docs.microsoft.com/en-us/previous-versions/azure/devops/2017/dec-11-vsts?view=tfs-2017#identify-flaky-tests>`_ and rerun failed tests.
 
 
 
@@ -105,8 +117,11 @@ This is a limited list, please submit an issue or pull request to expand it!
 
 * Gao, Zebao, Yalan Liang, Myra B. Cohen, Atif M. Memon, and Zhen Wang. "Making system user interactive tests repeatable: When and what should we control?." In *Software Engineering (ICSE), 2015 IEEE/ACM 37th IEEE International Conference on*, vol. 1, pp. 55-65. IEEE, 2015.  `PDF <http://www.cs.umd.edu/~atif/pubs/gao-icse15.pdf>`__
 * Palomba, Fabio, and Andy Zaidman. "Does refactoring of test smells induce fixing flaky tests?." In *Software Maintenance and Evolution (ICSME), 2017 IEEE International Conference on*, pp. 1-12. IEEE, 2017. `PDF in Google Drive <https://drive.google.com/file/d/10HdcCQiuQVgW3yYUJD-TSTq1NbYEprl0/view>`__
-*  Bell, Jonathan, Owolabi Legunsen, Michael Hilton, Lamyaa Eloussi, Tifany Yung, and Darko Marinov. "DeFlaker: Automatically detecting flaky tests." In *Proceedings of the 2018 International Conference on Software Engineering*. 2018. `PDF <https://www.jonbell.net/icse18-deflaker.pdf>`__
-
+* Bell, Jonathan, Owolabi Legunsen, Michael Hilton, Lamyaa Eloussi, Tifany Yung, and Darko Marinov. "DeFlaker: Automatically detecting flaky tests." In *Proceedings of the 2018 International Conference on Software Engineering*. 2018. `PDF <https://www.jonbell.net/icse18-deflaker.pdf#section-Research>`__
+* Dutta, Saikat and Shi, August and Choudhary, Rutvik and Zhang, Zhekun and Jain, Aryaman and Misailovic, Sasa. "Detecting flaky tests in probabilistic and machine learning applications." In *Proceedings of the 29th ACM SIGSOFT International Symposium on Software Testing and Analysis (ISSTA)*, pp. 211-224. ACM, 2020. `PDF <https://www.cs.cornell.edu/~saikatd/papers/flash-issta20.pdf>`__
+* Habchi, Sarra and Haben, Guillaume and Sohn, Jeongju and Franci, Adriano and Papadakis, Mike and Cordy, Maxime and Le Traon, Yves. "What Made This Test Flake? Pinpointing Classes Responsible for Test Flakiness." In Proceedings of the 38th IEEE International Conference on Software Maintenance and Evolution (ICSME), IEEE, 2022. `PDF <https://arxiv.org/abs/2207.10143>`__
+* Lamprou, Sokrates. "Non-deterministic tests and where to find them: Empirically investigating the relationship between flaky tests and test smells by examining test order dependency." Bachelor thesis, Department of Computer and Information Science, Linköping University, 2022. LIU-IDA/LITH-EX-G–19/056–SE. `PDF <https://www.diva-portal.org/smash/get/diva2:1713691/FULLTEXT01.pdf>`__
+* Leinen, Fabian and Elsner, Daniel and Pretschner, Alexander and Stahlbauer, Andreas and Sailer, Michael and Jürgens, Elmar. "Cost of Flaky Tests in Continuous Integration: An Industrial Case Study." Technical University of Munich and CQSE GmbH, Munich, Germany, 2023. `PDF <https://mediatum.ub.tum.de/doc/1730194/1730194.pdf>`__
 
 Resources
 ^^^^^^^^^
@@ -124,3 +139,13 @@ Resources
 
   * `Flaky Tests at Google and How We Mitigate Them <https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html>`_ by John Micco, 2016
   * `Where do Google's flaky tests come from? <https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html>`_  by Jeff Listfield, 2017
+
+* Dropbox:
+  * `Athena: Our automated build health management system <https://dropbox.tech/infrastructure/athena-our-automated-build-health-management-system>`_ by Utsav Shah, 2019
+  * `How To Manage Flaky Tests in your CI Workflows <https://mill-build.org/blog/4-flaky-tests.html>`_ by Li Haoyi, 2025
+
+* Uber:
+  * `Handling Flaky Unit Tests in Java <https://www.uber.com/blog/handling-flaky-tests-java/>`_ by Uber Engineering, 2021
+  * `Flaky Tests Overhaul at Uber <https://www.uber.com/blog/flaky-tests-overhaul/>`_ by Uber Engineering, 2024
+
+.. _pytest-xdist: https://github.com/pytest-dev/pytest-xdist
diff --git a/doc/en/explanation/goodpractices.rst b/doc/en/explanation/goodpractices.rst
index 32a14991ae2..51c0b960aed 100644
--- a/doc/en/explanation/goodpractices.rst
+++ b/doc/en/explanation/goodpractices.rst
@@ -12,41 +12,27 @@ For development, we recommend you use :mod:`venv` for virtual environments and
 as well as the ``pytest`` package itself.
 This ensures your code and dependencies are isolated from your system Python installation.
 
-Next, place a ``pyproject.toml`` file in the root of your package:
+Create a ``pyproject.toml`` file in the root of your repository as described in
+:doc:`packaging:tutorials/packaging-projects`.
+The first few lines should look like this:
 
 .. code-block:: toml
 
     [build-system]
-    requires = ["setuptools>=42", "wheel"]
-    build-backend = "setuptools.build_meta"
+    requires = ["hatchling"]
+    build-backend = "hatchling.build"
 
-and a ``setup.cfg`` file containing your package's metadata with the following minimum content:
+    [project]
+    name = "PACKAGENAME"
+    version = "PACKAGEVERSION"
 
-.. code-block:: ini
-
-    [metadata]
-    name = PACKAGENAME
-
-    [options]
-    packages = find:
-
-where ``PACKAGENAME`` is the name of your package.
-
-.. note::
-
-    If your pip version is older than ``21.3``, you'll also need a ``setup.py`` file:
-
-    .. code-block:: python
-
-        from setuptools import setup
-
-        setup()
+where ``PACKAGENAME`` and ``PACKAGEVERSION`` are the name and version of your package respectively.
 
 You can then install your package in "editable" mode by running from the same directory:
 
 .. code-block:: bash
 
-     pip install -e .
+    pip install -e .
 
 which lets you change your source code (both tests and application) and rerun tests at will.
 
@@ -65,8 +51,8 @@ Conventions for Python test discovery
 * In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_.
 * From those files, collect test items:
 
-  * ``test`` prefixed test functions or methods outside of class
-  * ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method)
+  * ``test`` prefixed test functions or methods outside of class.
+  * ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method). Methods decorated with ``@staticmethod`` and ``@classmethods`` are also considered.
 
 For examples of how to customize your test discovery :doc:`/example/pythoncollection`.
 
@@ -74,8 +60,10 @@ Within Python modules, ``pytest`` also discovers tests using the standard
 :ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
 
 
-Choosing a test layout / import rules
--------------------------------------
+.. _`test layout`:
+
+Choosing a test layout
+----------------------
 
 ``pytest`` supports two common test layouts:
 
@@ -89,11 +77,11 @@ to keep tests separate from actual application code (often a good idea):
 .. code-block:: text
 
     pyproject.toml
-    setup.cfg
-    mypkg/
-        __init__.py
-        app.py
-        view.py
+    src/
+        mypkg/
+            __init__.py
+            app.py
+            view.py
     tests/
         test_app.py
         test_view.py
@@ -103,83 +91,56 @@ This has the following benefits:
 
 * Your tests can run against an installed version after executing ``pip install .``.
 * Your tests can run against the local copy with an editable install after executing ``pip install --editable .``.
-* If you don't use an editable install and are relying on the fact that Python by default puts the current
-  directory in ``sys.path`` to import your package, you can execute ``python -m pytest`` to execute the tests against the
-  local copy directly, without using ``pip``.
 
-.. note::
+For new projects, we recommend to use ``importlib`` :ref:`import mode <import-modes>`
+(see which-import-mode_ for a detailed explanation).
+To this end, add the following to your ``pyproject.toml``:
 
-    See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
-    ``python -m pytest``.
+.. code-block:: toml
 
-Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode <import-modes>`
-(which is the default): your test files must have **unique names**, because
-``pytest`` will import them as *top-level* modules since there are no packages
-to derive a full package name from. In other words, the test files in the example above will
-be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to
-``sys.path``.
+    [tool.pytest.ini_options]
+    addopts = [
+        "--import-mode=importlib",
+    ]
 
-If you need to have test modules with the same name, you might add ``__init__.py`` files to your
-``tests`` folder and subfolders, changing them to packages:
+.. _src-layout:
 
-.. code-block:: text
+Generally, but especially if you use the default import mode ``prepend``,
+it is **strongly** suggested to use a ``src`` layout.
+Here, your application root package resides in a sub-directory of your root,
+i.e. ``src/mypkg/`` instead of ``mypkg``.
 
-    pyproject.toml
-    setup.cfg
-    mypkg/
-        ...
-    tests/
-        __init__.py
-        foo/
-            __init__.py
-            test_view.py
-        bar/
-            __init__.py
-            test_view.py
+This layout prevents a lot of common pitfalls and has many benefits,
+which are better explained in this excellent `blog post`_ by Ionel Cristian Mărieș.
 
-Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing
-you to have modules with the same name. But now this introduces a subtle problem: in order to load
-the test modules from the ``tests`` directory, pytest prepends the root of the repository to
-``sys.path``, which adds the side-effect that now ``mypkg`` is also importable.
+.. _blog post: https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>
 
-This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment,
-because you want to test the *installed* version of your package, not the local code from the repository.
+.. note::
 
-.. _`src-layout`:
+    If you do not use an editable install and use the ``src`` layout as above you need to extend the Python's
+    search path for module files to execute the tests against the local copy directly. You can do it in an
+    ad-hoc manner by setting the ``PYTHONPATH`` environment variable:
 
-In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
-sub-directory of your root:
+    .. code-block:: bash
 
-.. code-block:: text
+       PYTHONPATH=src pytest
 
-    pyproject.toml
-    setup.cfg
-    src/
-        mypkg/
-            __init__.py
-            app.py
-            view.py
-    tests/
-        __init__.py
-        foo/
-            __init__.py
-            test_view.py
-        bar/
-            __init__.py
-            test_view.py
+    or in a permanent manner by using the :confval:`pythonpath` configuration variable and adding the
+    following to your ``pyproject.toml``:
 
+    .. code-block:: toml
 
-This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent
-`blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
+        [tool.pytest.ini_options]
+        pythonpath = "src"
 
 .. note::
-    The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't have
-    any of the drawbacks above because ``sys.path`` is not changed when importing
-    test modules, so users that run
-    into this issue are strongly encouraged to try it and report if the new option works well for them.
 
-    The ``src`` directory layout is still strongly recommended however.
+    If you do not use an editable install and not use the ``src`` layout (``mypkg`` directly in the root
+    directory) you can rely on the fact that Python by default puts the current directory in ``sys.path`` to
+    import your package and run ``python -m pytest`` to execute the tests against the local copy directly.
 
+    See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
+    ``python -m pytest``.
 
 Tests as part of application code
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -191,12 +152,11 @@ want to distribute them along with your application:
 .. code-block:: text
 
     pyproject.toml
-    setup.cfg
-    mypkg/
+    [src/]mypkg/
         __init__.py
         app.py
         view.py
-        test/
+        tests/
             __init__.py
             test_app.py
             test_view.py
@@ -250,8 +210,58 @@ Note that this layout also works in conjunction with the ``src`` layout mentione
     to avoid surprises such as a test module getting imported twice.
 
     With ``--import-mode=importlib`` things are less convoluted because
-    pytest doesn't need to change ``sys.path`` or ``sys.modules``, making things
-    much less surprising.
+    pytest doesn't need to change ``sys.path``, making things much less
+    surprising.
+
+
+.. _which-import-mode:
+
+Choosing an import mode
+^^^^^^^^^^^^^^^^^^^^^^^
+
+For historical reasons, pytest defaults to the ``prepend`` :ref:`import mode <import-modes>`
+instead of the ``importlib`` import mode we recommend for new projects.
+The reason lies in the way the ``prepend`` mode works:
+
+Since there are no packages to derive a full package name from,
+``pytest`` will import your test files as *top-level* modules.
+The test files in the first example (:ref:`src layout <src-layout>`) would be imported as
+``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to ``sys.path``.
+
+This results in a drawback compared to the import mode ``importlib``:
+your test files must have **unique names**.
+
+If you need to have test modules with the same name,
+as a workaround you might add ``__init__.py`` files to your ``tests`` folder and subfolders,
+changing them to packages:
+
+.. code-block:: text
+
+    pyproject.toml
+    mypkg/
+        ...
+    tests/
+        __init__.py
+        foo/
+            __init__.py
+            test_view.py
+        bar/
+            __init__.py
+            test_view.py
+
+Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``,
+allowing you to have modules with the same name.
+But now this introduces a subtle problem:
+in order to load the test modules from the ``tests`` directory,
+pytest prepends the root of the repository to ``sys.path``,
+which adds the side-effect that now ``mypkg`` is also importable.
+
+This is problematic if you are using a tool like tox_ to test your package in a virtual environment,
+because you want to test the *installed* version of your package,
+not the local code from the repository.
+
+The ``importlib`` import mode does not have any of the drawbacks above,
+because ``sys.path`` is not changed when importing test modules.
 
 
 .. _`buildout`: http://www.buildout.org/en/latest/
@@ -263,8 +273,8 @@ tox
 
 Once you are done with your work and want to make sure that your actual
 package passes all tests you may want to look into :doc:`tox <tox:index>`, the
-virtualenv test automation tool and its :doc:`pytest support <tox:example/pytest>`.
-tox helps you to setup virtualenv environments with pre-defined
+virtualenv test automation tool.
+``tox`` helps you to setup virtualenv environments with pre-defined
 dependencies and then executing a pre-configured test command with
 options.  It will run tests against the installed package and not
 against your source code checkout, helping to detect packaging
@@ -286,3 +296,20 @@ See also `pypa/setuptools#1684 <https://github.com/pypa/setuptools/issues/1684>`
 
 setuptools intends to
 `remove the test command <https://github.com/pypa/setuptools/issues/931>`_.
+
+Checking with flake8-pytest-style
+---------------------------------
+
+In order to ensure that pytest is being used correctly in your project,
+it can be helpful to use the `flake8-pytest-style <https://github.com/m-burst/flake8-pytest-style>`_ flake8 plugin.
+
+flake8-pytest-style checks for common mistakes and coding style violations in pytest code,
+such as incorrect use of fixtures, test function names, and markers.
+By using this plugin, you can catch these errors early in the development process
+and ensure that your pytest code is consistent and easy to maintain.
+
+A list of the lints detected by flake8-pytest-style can be found on its `PyPI page <https://pypi.org/project/flake8-pytest-style/>`_.
+
+.. note::
+
+    flake8-pytest-style is not an official pytest project. Some of the rules enforce certain style choices, such as using `@pytest.fixture()` over `@pytest.fixture`, but you can configure the plugin to fit your preferred style.
diff --git a/doc/en/explanation/index.rst b/doc/en/explanation/index.rst
index 53910f1eb7b..2606d7d4b34 100644
--- a/doc/en/explanation/index.rst
+++ b/doc/en/explanation/index.rst
@@ -11,5 +11,7 @@ Explanation
    anatomy
    fixtures
    goodpractices
-   flaky
    pythonpath
+   types
+   ci
+   flaky
diff --git a/doc/en/explanation/pythonpath.rst b/doc/en/explanation/pythonpath.rst
index 2330356b863..e68f455cedf 100644
--- a/doc/en/explanation/pythonpath.rst
+++ b/doc/en/explanation/pythonpath.rst
@@ -10,23 +10,31 @@ Import modes
 
 pytest as a testing framework needs to import test modules and ``conftest.py`` files for execution.
 
-Importing files in Python (at least until recently) is a non-trivial processes, often requiring
-changing :data:`sys.path`. Some aspects of the
+Importing files in Python is a non-trivial process, so aspects of the
 import process can be controlled through the ``--import-mode`` command-line flag, which can assume
 these values:
 
-* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning*
-  of :py:data:`sys.path` if not already there, and then imported with the :func:`__import__ <__import__>` builtin.
+.. _`import-mode-prepend`:
 
-  This requires test module names to be unique when the test directory tree is not arranged in
-  packages, because the modules will put in :py:data:`sys.modules` after importing.
+* ``prepend`` (default): The directory path containing each module will be inserted into the *beginning*
+  of :py:data:`sys.path` if not already there, and then imported with
+  the :func:`importlib.import_module <importlib.import_module>` function.
+
+  It is highly recommended to arrange your test modules as packages by adding ``__init__.py`` files to your directories
+  containing tests. This will make the tests part of a proper Python package, allowing pytest to resolve their full
+  name (for example ``tests.core.test_core`` for ``test_core.py`` inside the ``tests.core`` package).
+
+  If the test directory tree is not arranged as packages, then each test file needs to have a unique name
+  compared to the other test files, otherwise pytest will raise an error if it finds two tests with the same name.
 
   This is the classic mechanism, dating back from the time Python 2 was still supported.
 
+.. _`import-mode-append`:
+
 * ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already
-  there, and imported with ``__import__``.
+  there, and imported with :func:`importlib.import_module <importlib.import_module>`.
 
-  This better allows to run test modules against installed versions of a package even if the
+  This better allows users to run test modules against installed versions of a package even if the
   package under test has the same import root. For example:
 
   ::
@@ -37,24 +45,79 @@ these values:
 
   the tests will run against the installed version
   of ``pkg_under_test`` when ``--import-mode=append`` is used whereas
-  with ``prepend`` they would pick up the local version. This kind of confusion is why
-  we advocate for using :ref:`src <src-layout>` layouts.
+  with ``prepend``, they would pick up the local version. This kind of confusion is why
+  we advocate for using :ref:`src-layouts <src-layout>`.
 
   Same as ``prepend``, requires test module names to be unique when the test directory tree is
   not arranged in packages, because the modules will put in :py:data:`sys.modules` after importing.
 
-* ``importlib``: new in pytest-6.0, this mode uses :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`.
+.. _`import-mode-importlib`:
+
+* ``importlib``: this mode uses more fine control mechanisms provided by :mod:`importlib` to import test modules, without changing :py:data:`sys.path`.
+
+  Advantages of this mode:
+
+  * pytest will not change :py:data:`sys.path` at all.
+  * Test module names do not need to be unique -- pytest will generate a unique name automatically based on the ``rootdir``.
+
+  Disadvantages:
+
+  * Test modules can't import each other.
+  * Testing utility modules in the tests directories (for example a ``tests.helpers`` module containing test-related functions/classes)
+    are not importable. The recommendation in this case it to place testing utility modules together with the application/library
+    code, for example ``app.testing.helpers``.
+
+    Important: by "test utility modules", we mean functions/classes which are imported by
+    other tests directly; this does not include fixtures, which should be placed in ``conftest.py`` files, along
+    with the test modules, and are discovered automatically by pytest.
+
+  It works like this:
+
+  1. Given a certain module path, for example ``tests/core/test_models.py``, derives a canonical name
+     like ``tests.core.test_models`` and tries to import it.
+
+     For non-test modules, this will work if they are accessible via :py:data:`sys.path`. So
+     for example, ``.env/lib/site-packages/app/core.py`` will be importable as ``app.core``.
+     This happens when plugins import non-test modules (for example doctesting).
+
+     If this step succeeds, the module is returned.
+
+     For test modules, unless they are reachable from :py:data:`sys.path`, this step will fail.
+
+  2. If the previous step fails, we import the module directly using ``importlib`` facilities, which lets us import it without
+     changing :py:data:`sys.path`.
+
+     Because Python requires the module to also be available in :py:data:`sys.modules`, pytest derives a unique name for it based
+     on its relative location from the ``rootdir``, and adds the module to :py:data:`sys.modules`.
+
+     For example, ``tests/core/test_models.py`` will end up being imported as the module ``tests.core.test_models``.
+
+  .. versionadded:: 6.0
+
+.. note::
+
+    Initially we intended to make ``importlib`` the default in future releases, however it is clear now that
+    it has its own set of drawbacks so the default will remain ``prepend`` for the foreseeable future.
+
+.. note::
+
+    By default, pytest will not attempt to resolve namespace packages automatically, but that can
+    be changed via the :confval:`consider_namespace_packages` configuration variable.
+
+.. seealso::
+
+    The :confval:`pythonpath` configuration variable.
+
+    The :confval:`consider_namespace_packages` configuration variable.
 
-  For this reason this doesn't require test module names to be unique, but also makes test
-  modules non-importable by each other.
+    :ref:`test layout`.
 
-  We intend to make ``importlib`` the default in future releases, depending on feedback.
 
 ``prepend`` and ``append`` import modes scenarios
 -------------------------------------------------
 
 Here's a list of scenarios when using ``prepend`` or ``append`` import modes where pytest needs to
-change ``sys.path`` in order to import test modules or ``conftest.py`` files, and the issues users
+change :py:data:`sys.path` in order to import test modules or ``conftest.py`` files, and the issues users
 might encounter because of that.
 
 Test modules / ``conftest.py`` files inside packages
@@ -83,7 +146,7 @@ pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a packa
 there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the
 last folder which still contains an ``__init__.py`` file in order to find the package *root* (in
 this case ``foo/``). To load the module, it will insert ``root/``  to the front of
-``sys.path`` (if not there already) in order to load
+:py:data:`sys.path` (if not there already) in order to load
 ``test_foo.py`` as the *module* ``foo.bar.tests.test_foo``.
 
 The same logic applies to the ``conftest.py`` file: it will be imported as ``foo.conftest`` module.
@@ -113,8 +176,8 @@ When executing:
 
 pytest will find ``foo/bar/tests/test_foo.py`` and realize it is NOT part of a package given that
 there's no ``__init__.py`` file in the same folder. It will then add ``root/foo/bar/tests`` to
-``sys.path`` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done
-with the ``conftest.py`` file by adding ``root/foo`` to ``sys.path`` to import it as ``conftest``.
+:py:data:`sys.path` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done
+with the ``conftest.py`` file by adding ``root/foo`` to :py:data:`sys.path` to import it as ``conftest``.
 
 For this reason this layout cannot have test modules with the same name, as they all will be
 imported in the global import namespace.
@@ -127,7 +190,7 @@ Invoking ``pytest`` versus ``python -m pytest``
 -----------------------------------------------
 
 Running pytest with ``pytest [...]`` instead of ``python -m pytest [...]`` yields nearly
-equivalent behaviour, except that the latter will add the current directory to ``sys.path``, which
+equivalent behaviour, except that the latter will add the current directory to :py:data:`sys.path`, which
 is standard ``python`` behavior.
 
 See also :ref:`invoke-python`.
diff --git a/doc/en/explanation/types.rst b/doc/en/explanation/types.rst
new file mode 100644
index 00000000000..827a2bf02b6
--- /dev/null
+++ b/doc/en/explanation/types.rst
@@ -0,0 +1,89 @@
+.. _types:
+
+Typing in pytest
+================
+
+.. note::
+    This page assumes the reader is familiar with Python's typing system and its advantages.
+
+    For more information, refer to `Python's Typing Documentation <https://docs.python.org/3/library/typing.html>`_.
+
+Why type tests?
+---------------
+
+Typing tests provides significant advantages:
+
+- **Readability:** Clearly defines expected inputs and outputs, improving readability, especially in complex or parameterized tests.
+
+- **Refactoring:** This is the main benefit in typing tests, as it will greatly help with refactoring, letting the type checker point out the necessary changes in both production and tests, without needing to run the full test suite.
+
+For production code, typing also helps catching some bugs that might not be caught by tests at all (regardless of coverage), for example:
+
+.. code-block:: python
+
+    def get_caption(target: int, items: list[tuple[int, str]]) -> str:
+        for value, caption in items:
+            if value == target:
+                return caption
+
+
+The type checker will correctly error out that the function might return `None`, however even a full coverage test suite might miss that case:
+
+.. code-block:: python
+
+    def test_get_caption() -> None:
+        assert get_caption(10, [(1, "foo"), (10, "bar")]) == "bar"
+
+
+Note the code above has 100% coverage, but the bug is not caught (of course the example is "obvious", but serves to illustrate the point).
+
+
+
+Using typing in test suites
+---------------------------
+
+To type fixtures in pytest, just add normal types to the fixture functions -- there is nothing special that needs to be done just because of the `fixture` decorator.
+
+.. code-block:: python
+
+    import pytest
+
+
+    @pytest.fixture
+    def sample_fixture() -> int:
+        return 38
+
+In the same manner, the fixtures passed to test functions need be annotated with the fixture's return type:
+
+.. code-block:: python
+
+    def test_sample_fixture(sample_fixture: int) -> None:
+        assert sample_fixture == 38
+
+From the POV of the type checker, it does not matter that `sample_fixture` is actually a fixture managed by pytest, all it matters to it is that `sample_fixture` is a parameter of type `int`.
+
+
+The same logic applies to :ref:`@pytest.mark.parametrize <@pytest.mark.parametrize>`:
+
+.. code-block:: python
+
+
+    @pytest.mark.parametrize("input_value, expected_output", [(1, 2), (5, 6), (10, 11)])
+    def test_increment(input_value: int, expected_output: int) -> None:
+        assert input_value + 1 == expected_output
+
+
+The same logic applies when typing fixture functions which receive other fixtures:
+
+.. code-block:: python
+
+    @pytest.fixture
+    def mock_env_user(monkeypatch: pytest.MonkeyPatch) -> None:
+        monkeypatch.setenv("USER", "TestingUser")
+
+
+Conclusion
+----------
+
+Incorporating typing into pytest tests enhances **clarity**, improves **debugging** and **maintenance**, and ensures **type safety**.
+These practices lead to a **robust**, **readable**, and **easily maintainable** test suite that is better equipped to handle future changes with minimal risk of errors.
diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst
index 3bf4527cfb5..bc5e7d3c515 100644
--- a/doc/en/funcarg_compare.rst
+++ b/doc/en/funcarg_compare.rst
@@ -11,14 +11,12 @@ funcarg mechanism, see :ref:`historical funcargs and pytest.funcargs`.
 If you are new to pytest, then you can simply ignore this
 section and read the other sections.
 
-.. currentmodule:: _pytest
-
 Shortcomings of the previous ``pytest_funcarg__`` mechanism
 --------------------------------------------------------------
 
 The pre pytest-2.3 funcarg mechanism calls a factory each time a
 funcarg for a test function is required.  If a factory wants to
-re-use a resource across different scopes, it often used
+reuse a resource across different scopes, it often used
 the ``request.cached_setup()`` helper to manage caching of
 resources.  Here is a basic example how we could implement
 a per-session Database object:
@@ -46,7 +44,7 @@ There are several limitations and difficulties with this approach:
 
 2. parametrizing the "db" resource is not straight forward:
    you need to apply a "parametrize" decorator or implement a
-   :py:func:`~hookspec.pytest_generate_tests` hook
+   :hook:`pytest_generate_tests` hook
    calling :py:func:`~pytest.Metafunc.parametrize` which
    performs parametrization at the places where the resource
    is used.  Moreover, you need to modify the factory to use an
@@ -94,15 +92,14 @@ Direct parametrization of funcarg resource factories
 
 Previously, funcarg factories could not directly cause parametrization.
 You needed to specify a ``@parametrize`` decorator on your test function
-or implement a ``pytest_generate_tests`` hook to perform
+or implement a :hook:`pytest_generate_tests` hook to perform
 parametrization, i.e. calling a test multiple times with different value
 sets.  pytest-2.3 introduces a decorator for use on the factory itself:
 
 .. code-block:: python
 
     @pytest.fixture(params=["mysql", "pg"])
-    def db(request):
-        ...  # use request.param
+    def db(request): ...  # use request.param
 
 Here the factory will be invoked twice (with the respective "mysql"
 and "pg" values set as ``request.param`` attributes) and all of
@@ -110,7 +107,7 @@ the tests requiring "db" will run twice as well.  The "mysql" and
 "pg" values will also be used for reporting the test-invocation variants.
 
 This new way of parametrizing funcarg factories should in many cases
-allow to re-use already written factories because effectively
+allow to reuse already written factories because effectively
 ``request.param`` was already used when test functions/classes were
 parametrized via
 :py:func:`metafunc.parametrize(indirect=True) <pytest.Metafunc.parametrize>` calls.
@@ -143,8 +140,7 @@ argument:
 .. code-block:: python
 
     @pytest.fixture()
-    def db(request):
-        ...
+    def db(request): ...
 
 The name under which the funcarg resource can be requested is ``db``.
 
@@ -153,8 +149,7 @@ aka:
 
 .. code-block:: python
 
-    def pytest_funcarg__db(request):
-        ...
+    def pytest_funcarg__db(request): ...
 
 
 But it is then not possible to define scoping and parametrization.
@@ -169,7 +164,7 @@ hook which are often used to setup global resources.  This suffers from
 several problems:
 
 1. in distributed testing the managing process would setup test resources
-   that are never needed because it only co-ordinates the test run
+   that are never needed because it only coordinates the test run
    activities of the worker processes.
 
 2. if you only perform a collection (with "--collect-only")
diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst
index a013645d1f5..41469de3864 100644
--- a/doc/en/getting-started.rst
+++ b/doc/en/getting-started.rst
@@ -9,7 +9,7 @@ Get Started
 Install ``pytest``
 ----------------------------------------
 
-``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3.
+``pytest`` requires: Python 3.8+ or PyPy3.
 
 1. Run the following command in your command line:
 
@@ -22,7 +22,7 @@ Install ``pytest``
 .. code-block:: bash
 
     $ pytest --version
-    pytest 6.3.0.dev685+g581b021aa.d20210922
+    pytest 8.3.5
 
 .. _`simpletest`:
 
@@ -47,8 +47,7 @@ The test
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 1 item
 
@@ -110,6 +109,8 @@ Execute the test function with “quiet” reporting mode:
 
     The ``-q/--quiet`` flag keeps the output brief in this and following examples.
 
+See :ref:`assertraises` for specifying more details about the expected exception.
+
 Group multiple tests in a class
 --------------------------------------------------------------
 
@@ -251,7 +252,7 @@ Continue reading
 Check out additional pytest resources to help you customize tests for your unique workflow:
 
 * ":ref:`usage`" for command line invocation examples
-* ":ref:`existingtestsuite`" for working with pre-existing tests
+* ":ref:`existingtestsuite`" for working with preexisting tests
 * ":ref:`mark`" for information on the ``pytest.mark`` mechanism
 * ":ref:`fixtures`" for providing a functional baseline to your tests
 * ":ref:`plugins`" for managing and writing plugins
diff --git a/doc/en/historical-notes.rst b/doc/en/historical-notes.rst
index 29ebbd5d199..be67036d6ca 100644
--- a/doc/en/historical-notes.rst
+++ b/doc/en/historical-notes.rst
@@ -107,12 +107,12 @@ Here is a non-exhaustive list of issues fixed by the new implementation:
 
 * Marker transfer incompatible with inheritance (:issue:`535`).
 
-More details can be found in the :pull:`original PR <3317>`.
+More details can be found in the :pr:`original PR <3317>`.
 
 .. note::
 
     in a future major release of pytest we will introduce class based markers,
-    at which point markers will no longer be limited to instances of :py:class:`~_pytest.mark.Mark`.
+    at which point markers will no longer be limited to instances of :py:class:`~pytest.Mark`.
 
 
 cache plugin integrated into the core
@@ -227,8 +227,7 @@ to use strings:
 
 
     @pytest.mark.skipif("sys.version_info >= (3,3)")
-    def test_function():
-        ...
+    def test_function(): ...
 
 During test function setup the skipif condition is evaluated by calling
 ``eval('sys.version_info >= (3,0)', namespace)``.  The namespace contains
@@ -262,8 +261,7 @@ configuration value which you might have added:
 .. code-block:: python
 
     @pytest.mark.skipif("not config.getvalue('db')")
-    def test_function():
-        ...
+    def test_function(): ...
 
 The equivalent with "boolean conditions" is:
 
diff --git a/doc/en/history.rst b/doc/en/history.rst
index 796a42486ca..bb5aa493022 100644
--- a/doc/en/history.rst
+++ b/doc/en/history.rst
@@ -139,7 +139,7 @@ project:
    which adds ``pytest`` (rather than ``py.test``) as the recommended
    command-line entry point
 
-Due to this history, it's diffcult to answer the question when pytest was started.
+Due to this history, it's difficult to answer the question when pytest was started.
 It depends what point should really be seen as the start of it all. One
 possible interpretation is to  pick Europython 2004, i.e. around June/July
 2004.
diff --git a/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst
index ab6cbdee7cf..6bc8f6fed33 100644
--- a/doc/en/how-to/assert.rst
+++ b/doc/en/how-to/assert.rst
@@ -29,8 +29,7 @@ you will see the return value of the function call:
 
     $ pytest test_assert1.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 1 item
 
@@ -55,14 +54,13 @@ operators. (See :ref:`tbreportdemo`).  This allows you to use the
 idiomatic python constructs without boilerplate code while not losing
 introspection information.
 
-However, if you specify a message with the assertion like this:
+If a message is specified with the assertion like this:
 
 .. code-block:: python
 
     assert a % 2 == 0, "value was odd, should be even"
 
-then no assertion introspection takes places at all and the message
-will be simply shown in the traceback.
+it is printed alongside the assertion introspection in the traceback.
 
 See :ref:`assert-details` for more information on assertion introspection.
 
@@ -100,6 +98,27 @@ and if you need to have access to the actual exception info you may use:
 the actual exception raised.  The main attributes of interest are
 ``.type``, ``.value`` and ``.traceback``.
 
+Note that ``pytest.raises`` will match the exception type or any subclasses (like the standard ``except`` statement).
+If you want to check if a block of code is raising an exact exception type, you need to check that explicitly:
+
+
+.. code-block:: python
+
+    def test_foo_not_implemented():
+        def foo():
+            raise NotImplementedError
+
+        with pytest.raises(RuntimeError) as excinfo:
+            foo()
+        assert excinfo.type is RuntimeError
+
+The :func:`pytest.raises` call will succeed, even though the function raises :class:`NotImplementedError`, because
+:class:`NotImplementedError` is a subclass of :class:`RuntimeError`; however the following `assert` statement will
+catch the problem.
+
+Matching exception messages
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
 You can pass a ``match`` keyword parameter to the context-manager to test
 that a regular expression matches on the string representation of an exception
 (similar to the ``TestCase.assertRaisesRegex`` method from ``unittest``):
@@ -117,36 +136,210 @@ that a regular expression matches on the string representation of an exception
         with pytest.raises(ValueError, match=r".* 123 .*"):
             myfunc()
 
-The regexp parameter of the ``match`` method is matched with the ``re.search``
-function, so in the above example ``match='123'`` would have worked as
-well.
+Notes:
+
+* The ``match`` parameter is matched with the :func:`re.search`
+  function, so in the above example ``match='123'`` would have worked as well.
+* The ``match`` parameter also matches against `PEP-678 <https://peps.python.org/pep-0678/>`__ ``__notes__``.
+
+
+.. _`assert-matching-exception-groups`:
+
+Assertions about expected exception groups
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When expecting a :exc:`BaseExceptionGroup` or :exc:`ExceptionGroup` you can use :class:`pytest.RaisesGroup`:
+
+.. code-block:: python
+
+    def test_exception_in_group():
+        with pytest.RaisesGroup(ValueError):
+            raise ExceptionGroup("group msg", [ValueError("value msg")])
+        with pytest.RaisesGroup(ValueError, TypeError):
+            raise ExceptionGroup("msg", [ValueError("foo"), TypeError("bar")])
+
 
-There's an alternate form of the :func:`pytest.raises` function where you pass
-a function that will be executed with the given ``*args`` and ``**kwargs`` and
-assert that the given exception is raised:
+It accepts a ``match`` parameter, that checks against the group message, and a ``check`` parameter that takes an arbitrary callable which it passes the group to, and only succeeds if the callable returns ``True``.
 
 .. code-block:: python
 
-    pytest.raises(ExpectedException, func, *args, **kwargs)
+    def test_raisesgroup_match_and_check():
+        with pytest.RaisesGroup(BaseException, match="my group msg"):
+            raise BaseExceptionGroup("my group msg", [KeyboardInterrupt()])
+        with pytest.RaisesGroup(
+            Exception, check=lambda eg: isinstance(eg.__cause__, ValueError)
+        ):
+            raise ExceptionGroup("", [TypeError()]) from ValueError()
+
+It is strict about structure and unwrapped exceptions, unlike :ref:`except* <except_star>`, so you might want to set the ``flatten_subgroups`` and/or ``allow_unwrapped`` parameters.
+
+.. code-block:: python
+
+    def test_structure():
+        with pytest.RaisesGroup(pytest.RaisesGroup(ValueError)):
+            raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
+        with pytest.RaisesGroup(ValueError, flatten_subgroups=True):
+            raise ExceptionGroup("1st group", [ExceptionGroup("2nd group", [ValueError()])])
+        with pytest.RaisesGroup(ValueError, allow_unwrapped=True):
+            raise ValueError
+
+To specify more details about the contained exception you can use :class:`pytest.RaisesExc`
+
+.. code-block:: python
+
+    def test_raises_exc():
+        with pytest.RaisesGroup(pytest.RaisesExc(ValueError, match="foo")):
+            raise ExceptionGroup("", (ValueError("foo")))
+
+They both supply a method :meth:`pytest.RaisesGroup.matches` :meth:`pytest.RaisesExc.matches` if you want to do matching outside of using it as a contextmanager. This can be helpful when checking ``.__context__`` or ``.__cause__``.
+
+.. code-block:: python
+
+    def test_matches():
+        exc = ValueError()
+        exc_group = ExceptionGroup("", [exc])
+        if RaisesGroup(ValueError).matches(exc_group):
+            ...
+        # helpful error is available in `.fail_reason` if it fails to match
+        r = RaisesExc(ValueError)
+        assert r.matches(e), r.fail_reason
+
+Check the documentation on :class:`pytest.RaisesGroup` and :class:`pytest.RaisesExc` for more details and examples.
+
+``ExceptionInfo.group_contains()``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. warning::
+
+   This helper makes it easy to check for the presence of specific exceptions, but it is very bad for checking that the group does *not* contain *any other exceptions*. So this will pass:
+
+    .. code-block:: python
+
+       class EXTREMELYBADERROR(BaseException):
+           """This is a very bad error to miss"""
+
+
+       def test_for_value_error():
+           with pytest.raises(ExceptionGroup) as excinfo:
+               excs = [ValueError()]
+               if very_unlucky():
+                   excs.append(EXTREMELYBADERROR())
+               raise ExceptionGroup("", excs)
+           # This passes regardless of if there's other exceptions.
+           assert excinfo.group_contains(ValueError)
+           # You can't simply list all exceptions you *don't* want to get here.
+
+
+   There is no good way of using :func:`excinfo.group_contains() <pytest.ExceptionInfo.group_contains>` to ensure you're not getting *any* other exceptions than the one you expected.
+   You should instead use :class:`pytest.RaisesGroup`, see :ref:`assert-matching-exception-groups`.
+
+You can also use the :func:`excinfo.group_contains() <pytest.ExceptionInfo.group_contains>`
+method to test for exceptions returned as part of an :class:`ExceptionGroup`:
+
+.. code-block:: python
+
+    def test_exception_in_group():
+        with pytest.raises(ExceptionGroup) as excinfo:
+            raise ExceptionGroup(
+                "Group message",
+                [
+                    RuntimeError("Exception 123 raised"),
+                ],
+            )
+        assert excinfo.group_contains(RuntimeError, match=r".* 123 .*")
+        assert not excinfo.group_contains(TypeError)
+
+The optional ``match`` keyword parameter works the same way as for
+:func:`pytest.raises`.
+
+By default ``group_contains()`` will recursively search for a matching
+exception at any level of nested ``ExceptionGroup`` instances. You can
+specify a ``depth`` keyword parameter if you only want to match an
+exception at a specific level; exceptions contained directly in the top
+``ExceptionGroup`` would match ``depth=1``.
+
+.. code-block:: python
+
+    def test_exception_in_group_at_given_depth():
+        with pytest.raises(ExceptionGroup) as excinfo:
+            raise ExceptionGroup(
+                "Group message",
+                [
+                    RuntimeError(),
+                    ExceptionGroup(
+                        "Nested group",
+                        [
+                            TypeError(),
+                        ],
+                    ),
+                ],
+            )
+        assert excinfo.group_contains(RuntimeError, depth=1)
+        assert excinfo.group_contains(TypeError, depth=2)
+        assert not excinfo.group_contains(RuntimeError, depth=2)
+        assert not excinfo.group_contains(TypeError, depth=1)
+
+Alternate `pytest.raises` form (legacy)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There is an alternate form of :func:`pytest.raises` where you pass
+a function that will be executed, along with ``*args`` and ``**kwargs``. :func:`pytest.raises`
+will then execute the function with those arguments and assert that the given exception is raised:
+
+.. code-block:: python
+
+    def func(x):
+        if x <= 0:
+            raise ValueError("x needs to be larger than zero")
+
+
+    pytest.raises(ValueError, func, x=-1)
 
 The reporter will provide you with helpful output in case of failures such as *no
 exception* or *wrong exception*.
 
-Note that it is also possible to specify a "raises" argument to
-``pytest.mark.xfail``, which checks that the test is failing in a more
+This form was the original :func:`pytest.raises` API, developed before the ``with`` statement was
+added to the Python language. Nowadays, this form is rarely used, with the context-manager form (using ``with``)
+being considered more readable.
+Nonetheless, this form is fully supported and not deprecated in any way.
+
+xfail mark and pytest.raises
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is also possible to specify a ``raises`` argument to
+:ref:`pytest.mark.xfail <pytest.mark.xfail ref>`, which checks that the test is failing in a more
 specific way than just having any exception raised:
 
 .. code-block:: python
 
+    def f():
+        raise IndexError()
+
+
     @pytest.mark.xfail(raises=IndexError)
     def test_f():
         f()
 
-Using :func:`pytest.raises` is likely to be better for cases where you are
-testing exceptions your own code is deliberately raising, whereas using
-``@pytest.mark.xfail`` with a check function is probably better for something
-like documenting unfixed bugs (where the test describes what "should" happen)
-or bugs in dependencies.
+
+This will only "xfail" if the test fails by raising ``IndexError`` or subclasses.
+
+* Using :ref:`pytest.mark.xfail <pytest.mark.xfail ref>` with the ``raises`` parameter is probably better for something
+  like documenting unfixed bugs (where the test describes what "should" happen) or bugs in dependencies.
+
+* Using :func:`pytest.raises` is likely to be better for cases where you are
+  testing exceptions your own code is deliberately raising, which is the majority of cases.
+
+You can also use :class:`pytest.RaisesGroup`:
+
+.. code-block:: python
+
+    def f():
+        raise ExceptionGroup("", [IndexError()])
+
+
+    @pytest.mark.xfail(raises=RaisesGroup(IndexError))
+    def test_f():
+        f()
 
 
 .. _`assertwarns`:
@@ -184,8 +377,7 @@ if you run this module:
 
     $ pytest test_assert2.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 1 item
 
@@ -199,11 +391,12 @@ if you run this module:
             set2 = set("8035")
     >       assert set1 == set2
     E       AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'}
+    E
     E         Extra items in the left set:
     E         '1'
     E         Extra items in the right set:
     E         '5'
-    E         Use -v to get the full diff
+    E         Use -v to get more diff
 
     test_assert2.py:4: AssertionError
     ========================= short test summary info ==========================
@@ -240,7 +433,7 @@ file which provides an alternative explanation for ``Foo`` objects:
        if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
            return [
                "Comparing Foo instances:",
-               "   vals: {} != {}".format(left.val, right.val),
+               f"   vals: {left.val} != {right.val}",
            ]
 
 now, given this test module:
diff --git a/doc/en/how-to/bash-completion.rst b/doc/en/how-to/bash-completion.rst
index 245dfd6d9a8..117ff7ec13b 100644
--- a/doc/en/how-to/bash-completion.rst
+++ b/doc/en/how-to/bash-completion.rst
@@ -5,7 +5,7 @@ How to set up bash completion
 =============================
 
 When using bash as your shell, ``pytest`` can use argcomplete
-(https://argcomplete.readthedocs.io/) for auto-completion.
+(https://kislyuk.github.io/argcomplete/) for auto-completion.
 For this ``argcomplete`` needs to be installed **and** enabled.
 
 Install argcomplete using:
diff --git a/doc/en/how-to/cache.rst b/doc/en/how-to/cache.rst
index 1ba048d955f..a3b2a862534 100644
--- a/doc/en/how-to/cache.rst
+++ b/doc/en/how-to/cache.rst
@@ -86,8 +86,7 @@ If you then run it with ``--lf``:
 
     $ pytest --lf
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
     run-last-failure: rerun previous 2 failures
@@ -133,8 +132,7 @@ of ``FF`` and dots):
 
     $ pytest --ff
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 50 items
     run-last-failure: rerun previous 2 failures first
@@ -178,14 +176,21 @@ with more recent files coming first.
 Behavior when no tests failed in the last run
 ---------------------------------------------
 
-When no tests failed in the last run, or when no cached ``lastfailed`` data was
-found, ``pytest`` can be configured either to run all of the tests or no tests,
-using the ``--last-failed-no-failures`` option, which takes one of the following values:
+The ``--lfnf/--last-failed-no-failures`` option governs the behavior of ``--last-failed``.
+Determines whether to execute tests when there are no previously (known)
+failures or when no cached ``lastfailed`` data was found.
+
+There are two options:
+
+* ``all``:  when there are no known test failures, runs all tests (the full test suite). This is the default.
+* ``none``: when there are no known test failures, just emits a message stating this and exit successfully.
+
+Example:
 
 .. code-block:: bash
 
-    pytest --last-failed --last-failed-no-failures all    # run all tests (default behavior)
-    pytest --last-failed --last-failed-no-failures none   # run no tests and exit
+    pytest --last-failed --last-failed-no-failures all    # runs the full test suite (default behavior)
+    pytest --last-failed --last-failed-no-failures none   # runs no tests and exits successfully
 
 The new config.cache object
 --------------------------------
@@ -194,14 +199,13 @@ The new config.cache object
 
 Plugins or conftest.py support code can get a cached value using the
 pytest ``config`` object.  Here is a basic example plugin which
-implements a :ref:`fixture <fixture>` which re-uses previously created state
+implements a :ref:`fixture <fixture>` which reuses previously created state
 across pytest invocations:
 
 .. code-block:: python
 
     # content of test_caching.py
     import pytest
-    import time
 
 
     def expensive_computation():
@@ -209,12 +213,12 @@ across pytest invocations:
 
 
     @pytest.fixture
-    def mydata(request):
-        val = request.config.cache.get("example/value", None)
+    def mydata(pytestconfig):
+        val = pytestconfig.cache.get("example/value", None)
         if val is None:
             expensive_computation()
             val = 42
-            request.config.cache.set("example/value", val)
+            pytestconfig.cache.set("example/value", val)
         return val
 
 
@@ -236,7 +240,7 @@ If you run this command for the first time, you can see the print statement:
     >       assert mydata == 23
     E       assert 42 == 23
 
-    test_caching.py:20: AssertionError
+    test_caching.py:19: AssertionError
     -------------------------- Captured stdout setup ---------------------------
     running expensive computation...
     ========================= short test summary info ==========================
@@ -259,7 +263,7 @@ the cache and nothing will be printed:
     >       assert mydata == 23
     E       assert 42 == 23
 
-    test_caching.py:20: AssertionError
+    test_caching.py:19: AssertionError
     ========================= short test summary info ==========================
     FAILED test_caching.py::test_function - assert 42 == 23
     1 failed in 0.12s
@@ -277,8 +281,7 @@ You can always peek at the content of the cache using the
 
     $ pytest --cache-show
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     cachedir: /home/sweet/project/.pytest_cache
     --------------------------- cache values for '*' ---------------------------
@@ -300,8 +303,7 @@ filtering:
 
     $ pytest --cache-show example/*
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     cachedir: /home/sweet/project/.pytest_cache
     ----------------------- cache values for 'example/*' -----------------------
diff --git a/doc/en/how-to/capture-stdout-stderr.rst b/doc/en/how-to/capture-stdout-stderr.rst
index 14628df6164..e6affd80ea1 100644
--- a/doc/en/how-to/capture-stdout-stderr.rst
+++ b/doc/en/how-to/capture-stdout-stderr.rst
@@ -4,6 +4,12 @@
 How to capture stdout/stderr output
 =========================================================
 
+Pytest intercepts stdout and stderr as configured by the ``--capture=``
+command-line argument or by using fixtures. The ``--capture=`` flag configures
+reporting, whereas the fixtures offer more granular control and allows
+inspection of output during testing. The reports can be customized with the
+`-r flag <../reference/reference.html#command-line-flags>`_.
+
 Default stdout/stderr/stdin capturing behaviour
 ---------------------------------------------------------
 
@@ -83,8 +89,7 @@ of the failing function and hide the other one:
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
 
@@ -107,9 +112,10 @@ of the failing function and hide the other one:
 Accessing captured output from a test function
 ---------------------------------------------------
 
-The ``capsys``, ``capsysbinary``, ``capfd``, and ``capfdbinary`` fixtures
-allow access to stdout/stderr output created during test execution.  Here is
-an example test function that performs some output related checks:
+The :fixture:`capsys`, :fixture:`capteesys`, :fixture:`capsysbinary`, :fixture:`capfd`, and :fixture:`capfdbinary`
+fixtures allow access to ``stdout``/``stderr`` output created during test execution.
+
+Here is an example test function that performs some output related checks:
 
 .. code-block:: python
 
@@ -126,40 +132,27 @@ an example test function that performs some output related checks:
 The ``readouterr()`` call snapshots the output so far -
 and capturing will be continued.  After the test
 function finishes the original streams will
-be restored.  Using ``capsys`` this way frees your
+be restored.  Using :fixture:`capsys` this way frees your
 test from having to care about setting/resetting
 output streams and also interacts well with pytest's
 own per-test capturing.
 
-If you want to capture on filedescriptor level you can use
-the ``capfd`` fixture which offers the exact
-same interface but allows to also capture output from
-libraries or subprocesses that directly write to operating
-system level output streams (FD1 and FD2).
-
-
-
-The return value from ``readouterr`` changed to a ``namedtuple`` with two attributes, ``out`` and ``err``.
-
+The return value of ``readouterr()`` is a ``namedtuple`` with two attributes, ``out`` and ``err``.
 
-
-If the code under test writes non-textual data, you can capture this using
-the ``capsysbinary`` fixture which instead returns ``bytes`` from
+If the code under test writes non-textual data (``bytes``), you can capture this using
+the :fixture:`capsysbinary` fixture which instead returns ``bytes`` from
 the ``readouterr`` method.
 
+If you want to capture at the file descriptor level you can use
+the :fixture:`capfd` fixture which offers the exact
+same interface but allows to also capture output from
+libraries or subprocesses that directly write to operating
+system level output streams (FD1 and FD2). Similarly to :fixture:`capsysbinary`, :fixture:`capfdbinary` can be
+used to capture ``bytes`` at the file descriptor level.
 
 
-
-If the code under test writes non-textual data, you can capture this using
-the ``capfdbinary`` fixture which instead returns ``bytes`` from
-the ``readouterr`` method.  The ``capfdbinary`` fixture operates on the
-filedescriptor level.
-
-
-
-
-To temporarily disable capture within a test, both ``capsys``
-and ``capfd`` have a ``disabled()`` method that can be used
+To temporarily disable capture within a test, the capture fixtures
+have a ``disabled()`` method that can be used
 as a context manager, disabling capture inside the ``with`` block:
 
 .. code-block:: python
diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst
index 4c89905e2c6..4b1de6f3704 100644
--- a/doc/en/how-to/capture-warnings.rst
+++ b/doc/en/how-to/capture-warnings.rst
@@ -28,8 +28,7 @@ Running pytest now produces this output:
 
     $ pytest test_show_warnings.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 1 item
 
@@ -43,8 +42,20 @@ Running pytest now produces this output:
     -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
     ======================= 1 passed, 1 warning in 0.12s =======================
 
-The ``-W`` flag can be passed to control which warnings will be displayed or even turn
-them into errors:
+.. _`controlling-warnings`:
+
+Controlling warnings
+--------------------
+
+Similar to Python's `warning filter`_ and :option:`-W option <python:-W>` flag, pytest provides
+its own ``-W`` flag to control which warnings are ignored, displayed, or turned into
+errors. See the `warning filter`_ documentation for more
+advanced use-cases.
+
+.. _`warning filter`: https://docs.python.org/3/library/warnings.html#warning-filter
+
+This code sample shows how to treat any ``UserWarning`` category class of warning
+as an error:
 
 .. code-block:: pytest
 
@@ -97,9 +108,18 @@ all other warnings into errors.
 When a warning matches more than one option in the list, the action for the last matching option
 is performed.
 
-Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own
-:option:`-W option <python:-W>` and :func:`warnings.simplefilter`, so please refer to those sections in the Python
-documentation for other examples and advanced usage.
+
+.. note::
+
+    The ``-W`` flag and the ``filterwarnings`` ini option use warning filters that are
+    similar in structure, but each configuration option interprets its filter
+    differently. For example, *message* in ``filterwarnings`` is a string containing a
+    regular expression that the start of the warning message must match,
+    case-insensitively, while *message* in ``-W`` is a literal string that the start of
+    the warning message must contain (case-insensitively), ignoring any whitespace at
+    the start or end of message. Consult the `warning filter`_ documentation for more
+    details.
+
 
 .. _`filterwarnings`:
 
@@ -108,7 +128,7 @@ documentation for other examples and advanced usage.
 
 
 
-You can use the ``@pytest.mark.filterwarnings`` to add warning filters to specific test items,
+You can use the :ref:`@pytest.mark.filterwarnings <pytest.mark.filterwarnings ref>` mark to add warning filters to specific test items,
 allowing you to have finer control of which warnings should be captured at test, class or
 even module level:
 
@@ -127,10 +147,30 @@ even module level:
         assert api_v1() == 1
 
 
+You can specify multiple filters with separate decorators:
+
+.. code-block:: python
+
+    # Ignore "api v1" warnings, but fail on all other warnings
+    @pytest.mark.filterwarnings("ignore:api v1")
+    @pytest.mark.filterwarnings("error")
+    def test_one():
+        assert api_v1() == 1
+
+.. important::
+
+    Regarding decorator order and filter precedence:
+    it's important to remember that decorators are evaluated in reverse order,
+    so you have to list the warning filters in the reverse order
+    compared to traditional :py:func:`warnings.filterwarnings` and :option:`-W option <python:-W>` usage.
+    This means in practice that filters from earlier :ref:`@pytest.mark.filterwarnings <pytest.mark.filterwarnings ref>` decorators
+    take precedence over filters from later decorators, as illustrated in the example above.
+
+
 Filters applied using a mark take precedence over filters passed on the command line or configured
-by the ``filterwarnings`` ini option.
+by the :confval:`filterwarnings` ini option.
 
-You may apply a filter to all tests of a class by using the ``filterwarnings`` mark as a class
+You may apply a filter to all tests of a class by using the :ref:`filterwarnings <pytest.mark.filterwarnings ref>` mark as a class
 decorator or to all tests in a module by setting the :globalvar:`pytestmark` variable:
 
 .. code-block:: python
@@ -139,6 +179,13 @@ decorator or to all tests in a module by setting the :globalvar:`pytestmark` var
     pytestmark = pytest.mark.filterwarnings("error")
 
 
+.. note::
+
+    If you want to apply multiple filters
+    (by assigning a list of :ref:`filterwarnings <pytest.mark.filterwarnings ref>` mark to :globalvar:`pytestmark`),
+    you must use the traditional :py:func:`warnings.filterwarnings` ordering approach (later filters take precedence),
+    which is the reverse of the decorator approach mentioned above.
+
 
 *Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
 *plugin.*
@@ -170,11 +217,14 @@ using an external system.
 DeprecationWarning and PendingDeprecationWarning
 ------------------------------------------------
 
-
 By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings from
 user code and third-party libraries, as recommended by :pep:`565`.
 This helps users keep their code modern and avoid breakages when deprecated warnings are effectively removed.
 
+However, in the specific case where users capture any type of warnings in their test, either with
+:func:`pytest.warns`, :func:`pytest.deprecated_call` or using the :fixture:`recwarn` fixture,
+no warning will be displayed at all.
+
 Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over
 (such as third-party libraries), in which case you might use the warning filters options (ini or marks) to ignore
 those warnings.
@@ -191,13 +241,16 @@ For example:
 This will ignore all warnings of type ``DeprecationWarning`` where the start of the message matches
 the regular expression ``".*U.*mode is deprecated"``.
 
+See :ref:`@pytest.mark.filterwarnings <filterwarnings>` and
+:ref:`Controlling warnings <controlling-warnings>` for more examples.
+
 .. note::
 
     If warnings are configured at the interpreter level, using
     the :envvar:`python:PYTHONWARNINGS` environment variable or the
     ``-W`` command-line option, pytest will not configure any filters by default.
 
-    Also pytest doesn't follow :pep:`506` suggestion of resetting all warning filters because
+    Also pytest doesn't follow :pep:`565` suggestion of resetting all warning filters because
     it might break test suites that configure warning filters themselves
     by calling :func:`warnings.simplefilter` (see :issue:`2430` for an example of that).
 
@@ -239,14 +292,15 @@ when called with a ``17`` argument.
 Asserting warnings with the warns function
 ------------------------------------------
 
-
-
 You can check that code raises a particular warning using :func:`pytest.warns`,
-which works in a similar manner to :ref:`raises <assertraises>`:
+which works in a similar manner to :ref:`raises <assertraises>` (except that
+:ref:`raises <assertraises>` does not capture all exceptions, only the
+``expected_exception``):
 
 .. code-block:: python
 
     import warnings
+
     import pytest
 
 
@@ -254,21 +308,35 @@ which works in a similar manner to :ref:`raises <assertraises>`:
         with pytest.warns(UserWarning):
             warnings.warn("my warning", UserWarning)
 
-The test will fail if the warning in question is not raised. The keyword
-argument ``match`` to assert that the exception matches a text or regex::
+The test will fail if the warning in question is not raised. Use the keyword
+argument ``match`` to assert that the warning matches a text or regex.
+To match a literal string that may contain regular expression metacharacters like ``(`` or ``.``, the pattern can
+first be escaped with ``re.escape``.
+
+Some examples:
+
+.. code-block:: pycon
+
 
-    >>> with warns(UserWarning, match='must be 0 or None'):
+    >>> with warns(UserWarning, match="must be 0 or None"):
     ...     warnings.warn("value must be 0 or None", UserWarning)
+    ...
 
-    >>> with warns(UserWarning, match=r'must be \d+$'):
+    >>> with warns(UserWarning, match=r"must be \d+$"):
     ...     warnings.warn("value must be 42", UserWarning)
+    ...
 
-    >>> with warns(UserWarning, match=r'must be \d+$'):
+    >>> with warns(UserWarning, match=r"must be \d+$"):
     ...     warnings.warn("this is not here", UserWarning)
+    ...
     Traceback (most recent call last):
       ...
     Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
 
+    >>> with warns(UserWarning, match=re.escape("issue with foo() func")):
+    ...     warnings.warn("issue with foo() func")
+    ...
+
 You can also call :func:`pytest.warns` on a function or code string:
 
 .. code-block:: python
@@ -291,10 +359,10 @@ additional information:
     assert record[0].message.args[0] == "another warning"
 
 Alternatively, you can examine raised warnings in detail using the
-:ref:`recwarn <recwarn>` fixture (see below).
+:fixture:`recwarn` fixture (see :ref:`below <recwarn>`).
 
 
-The :ref:`recwarn <recwarn>` fixture automatically ensures to reset the warnings
+The :fixture:`recwarn` fixture automatically ensures to reset the warnings
 filter at the end of the test, so no global state is leaked.
 
 .. _`recording warnings`:
@@ -304,8 +372,8 @@ filter at the end of the test, so no global state is leaked.
 Recording warnings
 ------------------
 
-You can record raised warnings either using :func:`pytest.warns` or with
-the ``recwarn`` fixture.
+You can record raised warnings either using the :func:`pytest.warns` context manager or with
+the :fixture:`recwarn` fixture.
 
 To record with :func:`pytest.warns` without asserting anything about the warnings,
 pass no arguments as the expected warning type and it will default to a generic Warning:
@@ -320,7 +388,7 @@ pass no arguments as the expected warning type and it will default to a generic
     assert str(record[0].message) == "user"
     assert str(record[1].message) == "runtime"
 
-The ``recwarn`` fixture will record warnings for the whole function:
+The :fixture:`recwarn` fixture will record warnings for the whole function:
 
 .. code-block:: python
 
@@ -336,14 +404,54 @@ The ``recwarn`` fixture will record warnings for the whole function:
         assert w.filename
         assert w.lineno
 
-Both ``recwarn`` and :func:`pytest.warns` return the same interface for recorded
-warnings: a WarningsRecorder instance. To view the recorded warnings, you can
+Both the :fixture:`recwarn` fixture and the :func:`pytest.warns` context manager return the same interface for recorded
+warnings: a :class:`~_pytest.recwarn.WarningsRecorder` instance. To view the recorded warnings, you can
 iterate over this instance, call ``len`` on it to get the number of recorded
 warnings, or index into it to get a particular recorded warning.
 
-.. currentmodule:: _pytest.warnings
 
-Full API: :class:`~_pytest.recwarn.WarningsRecorder`.
+.. _`warns use cases`:
+
+Additional use cases of warnings in tests
+-----------------------------------------
+
+Here are some use cases involving warnings that often come up in tests, and suggestions on how to deal with them:
+
+- To ensure that **at least one** of the indicated warnings is issued, use:
+
+.. code-block:: python
+
+    def test_warning():
+        with pytest.warns((RuntimeWarning, UserWarning)):
+            ...
+
+- To ensure that **only** certain warnings are issued, use:
+
+.. code-block:: python
+
+    def test_warning(recwarn):
+        ...
+        assert len(recwarn) == 1
+        user_warning = recwarn.pop(UserWarning)
+        assert issubclass(user_warning.category, UserWarning)
+
+-  To ensure that **no** warnings are emitted, use:
+
+.. code-block:: python
+
+    def test_warning():
+        with warnings.catch_warnings():
+            warnings.simplefilter("error")
+            ...
+
+- To suppress warnings, use:
+
+.. code-block:: python
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore")
+        ...
+
 
 .. _custom_failure_messages:
 
@@ -404,3 +512,18 @@ Please read our :ref:`backwards-compatibility` to learn how we proceed about dep
 features.
 
 The full list of warnings is listed in :ref:`the reference documentation <warnings ref>`.
+
+
+.. _`resource-warnings`:
+
+Resource Warnings
+-----------------
+
+Additional information of the source of a :class:`ResourceWarning` can be obtained when captured by pytest if
+:mod:`tracemalloc` module is enabled.
+
+One convenient way to enable :mod:`tracemalloc` when running tests is to set the :envvar:`PYTHONTRACEMALLOC` to a large
+enough number of frames (say ``20``, but that number is application dependent).
+
+For more information, consult the `Python Development Mode <https://docs.python.org/3/library/devmode.html>`__
+section in the Python documentation.
diff --git a/doc/en/how-to/doctest.rst b/doc/en/how-to/doctest.rst
index 29e689e1290..c2a6cc8e958 100644
--- a/doc/en/how-to/doctest.rst
+++ b/doc/en/how-to/doctest.rst
@@ -30,8 +30,7 @@ then you can just invoke ``pytest`` directly:
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 1 item
 
@@ -59,8 +58,7 @@ and functions, including from test modules:
 
     $ pytest --doctest-modules
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
 
@@ -128,14 +126,17 @@ pytest also introduces new options:
   in expected doctest output.
 
 * ``NUMBER``: when enabled, floating-point numbers only need to match as far as
-  the precision you have written in the expected doctest output. For example,
-  the following output would only need to match to 2 decimal places::
+  the precision you have written in the expected doctest output. The numbers are
+  compared using :func:`pytest.approx` with relative tolerance equal to the
+  precision. For example, the following output would only need to match to 2
+  decimal places when comparing ``3.14`` to
+  ``pytest.approx(math.pi, rel=10**-2)``::
 
       >>> math.pi
       3.14
 
-  If you wrote ``3.1416`` then the actual output would need to match to 4
-  decimal places; and so on.
+  If you wrote ``3.1416`` then the actual output would need to match to
+  approximately 4 decimal places; and so on.
 
   This avoids false positives caused by limited floating-point precision, like
   this::
@@ -223,6 +224,7 @@ place the objects you want to appear in the doctest namespace:
 .. code-block:: python
 
     # content of conftest.py
+    import pytest
     import numpy
 
 
@@ -241,7 +243,6 @@ which can then be used in your doctests directly:
         >>> len(a)
         10
         """
-        pass
 
 Note that like the normal ``conftest.py``, the fixtures are discovered in the directory tree conftest is in.
 Meaning that if you put your doctest with your source code, the relevant conftest.py needs to be in the same directory tree.
diff --git a/doc/en/how-to/existingtestsuite.rst b/doc/en/how-to/existingtestsuite.rst
index 9909e7d113a..1c37023c72a 100644
--- a/doc/en/how-to/existingtestsuite.rst
+++ b/doc/en/how-to/existingtestsuite.rst
@@ -4,8 +4,8 @@ How to use pytest with an existing test suite
 ==============================================
 
 Pytest can be used with most existing test suites, but its
-behavior differs from other test runners such as :ref:`nose <noseintegration>` or
-Python's default unittest framework.
+behavior differs from other test runners such as Python's
+default unittest framework.
 
 Before using this section you will want to :ref:`install pytest <getstarted>`.
 
diff --git a/doc/en/how-to/failures.rst b/doc/en/how-to/failures.rst
index ef87550915a..b3d0c155b48 100644
--- a/doc/en/how-to/failures.rst
+++ b/doc/en/how-to/failures.rst
@@ -135,10 +135,6 @@ Warning about unraisable exceptions and unhandled thread exceptions
 
 .. versionadded:: 6.2
 
-.. note::
-
-    These features only work on Python>=3.8.
-
 Unhandled exceptions are exceptions that are raised in a situation in which
 they cannot propagate to a caller. The most common case is an exception raised
 in a :meth:`__del__ <object.__del__>` implementation.
diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst
index e0ac36e792d..8f84e4867a6 100644
--- a/doc/en/how-to/fixtures.rst
+++ b/doc/en/how-to/fixtures.rst
@@ -398,9 +398,10 @@ access the fixture function:
 .. code-block:: python
 
     # content of conftest.py
-    import pytest
     import smtplib
 
+    import pytest
+
 
     @pytest.fixture(scope="module")
     def smtp_connection():
@@ -432,8 +433,7 @@ marked ``smtp_connection`` fixture function.  Running the test looks like this:
 
     $ pytest test_module.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
 
@@ -494,7 +494,7 @@ Fixtures are created when first requested by a test, and are destroyed based on
 * ``function``: the default scope, the fixture is destroyed at the end of the test.
 * ``class``: the fixture is destroyed during teardown of the last test in the class.
 * ``module``: the fixture is destroyed during teardown of the last test in the module.
-* ``package``: the fixture is destroyed during teardown of the last test in the package.
+* ``package``: the fixture is destroyed during teardown of the last test in the package where the fixture is defined, including sub-packages and sub-directories within it.
 * ``session``: the fixture is destroyed at the end of the test session.
 
 .. note::
@@ -610,10 +610,10 @@ Here's what that might look like:
 .. code-block:: python
 
     # content of test_emaillib.py
-    import pytest
-
     from emaillib import Email, MailAdminClient
 
+    import pytest
+
 
     @pytest.fixture
     def mail_admin():
@@ -631,6 +631,7 @@ Here's what that might look like:
     def receiving_user(mail_admin):
         user = mail_admin.create_user()
         yield user
+        user.clear_mailbox()
         mail_admin.delete_user(user)
 
 
@@ -684,10 +685,10 @@ Here's how the previous example would look using the ``addfinalizer`` method:
 .. code-block:: python
 
     # content of test_emaillib.py
-    import pytest
-
     from emaillib import Email, MailAdminClient
 
+    import pytest
+
 
     @pytest.fixture
     def mail_admin():
@@ -737,6 +738,87 @@ does offer some nuances for when you're in a pinch.
    .                                                                    [100%]
    1 passed in 0.12s
 
+Note on finalizer order
+""""""""""""""""""""""""
+
+Finalizers are executed in a first-in-last-out order.
+For yield fixtures, the first teardown code to run is from the right-most fixture, i.e. the last test parameter.
+
+
+.. code-block:: python
+
+    # content of test_finalizers.py
+    import pytest
+
+
+    def test_bar(fix_w_yield1, fix_w_yield2):
+        print("test_bar")
+
+
+    @pytest.fixture
+    def fix_w_yield1():
+        yield
+        print("after_yield_1")
+
+
+    @pytest.fixture
+    def fix_w_yield2():
+        yield
+        print("after_yield_2")
+
+
+.. code-block:: pytest
+
+    $ pytest -s test_finalizers.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
+    rootdir: /home/sweet/project
+    collected 1 item
+
+    test_finalizers.py test_bar
+    .after_yield_2
+    after_yield_1
+
+
+    ============================ 1 passed in 0.12s =============================
+
+For finalizers, the first fixture to run is last call to `request.addfinalizer`.
+
+.. code-block:: python
+
+    # content of test_finalizers.py
+    from functools import partial
+    import pytest
+
+
+    @pytest.fixture
+    def fix_w_finalizers(request):
+        request.addfinalizer(partial(print, "finalizer_2"))
+        request.addfinalizer(partial(print, "finalizer_1"))
+
+
+    def test_bar(fix_w_finalizers):
+        print("test_bar")
+
+
+.. code-block:: pytest
+
+    $ pytest -s test_finalizers.py
+    =========================== test session starts ============================
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
+    rootdir: /home/sweet/project
+    collected 1 item
+
+    test_finalizers.py test_bar
+    .finalizer_1
+    finalizer_2
+
+
+    ============================ 1 passed in 0.12s =============================
+
+This is so because yield fixtures use `addfinalizer` behind the scenes: when the fixture executes, `addfinalizer` registers a function that resumes the generator, which in turn calls the teardown code.
+
+
 .. _`safe teardowns`:
 
 Safe teardowns
@@ -753,10 +835,10 @@ above):
 .. code-block:: python
 
     # content of test_emaillib.py
-    import pytest
-
     from emaillib import Email, MailAdminClient
 
+    import pytest
+
 
     @pytest.fixture
     def setup():
@@ -1031,16 +1113,17 @@ read an optional server URL from the test module which uses our fixture:
 .. code-block:: python
 
     # content of conftest.py
-    import pytest
     import smtplib
 
+    import pytest
+
 
     @pytest.fixture(scope="module")
     def smtp_connection(request):
         server = getattr(request.module, "smtpserver", "smtp.gmail.com")
         smtp_connection = smtplib.SMTP(server, 587, timeout=5)
         yield smtp_connection
-        print("finalizing {} ({})".format(smtp_connection, server))
+        print(f"finalizing {smtp_connection} ({server})")
         smtp_connection.close()
 
 We use the ``request.module`` attribute to optionally obtain an
@@ -1154,7 +1237,6 @@ If the data created by the factory requires managing, the fixture can take care
 
     @pytest.fixture
     def make_customer_record():
-
         created_records = []
 
         def _make_customer_record(name):
@@ -1189,20 +1271,21 @@ configured in multiple ways.
 Extending the previous example, we can flag the fixture to create two
 ``smtp_connection`` fixture instances which will cause all tests using the fixture
 to run twice.  The fixture function gets access to each parameter
-through the special :py:class:`request <FixtureRequest>` object:
+through the special :py:class:`request <pytest.FixtureRequest>` object:
 
 .. code-block:: python
 
     # content of conftest.py
-    import pytest
     import smtplib
 
+    import pytest
+
 
     @pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"])
     def smtp_connection(request):
         smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
         yield smtp_connection
-        print("finalizing {}".format(smtp_connection))
+        print(f"finalizing {smtp_connection}")
         smtp_connection.close()
 
 The main change is the declaration of ``params`` with
@@ -1331,28 +1414,30 @@ Running the above tests results in the following test IDs being used:
 
    $ pytest --collect-only
    =========================== test session starts ============================
-   platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-   cachedir: .pytest_cache
+   platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
    rootdir: /home/sweet/project
-   collected 11 items
-
-   <Module test_anothersmtp.py>
-     <Function test_showhelo[smtp.gmail.com]>
-     <Function test_showhelo[mail.python.org]>
-   <Module test_emaillib.py>
-     <Function test_email_received>
-   <Module test_ids.py>
-     <Function test_a[spam]>
-     <Function test_a[ham]>
-     <Function test_b[eggs]>
-     <Function test_b[1]>
-   <Module test_module.py>
-     <Function test_ehlo[smtp.gmail.com]>
-     <Function test_noop[smtp.gmail.com]>
-     <Function test_ehlo[mail.python.org]>
-     <Function test_noop[mail.python.org]>
-
-   ======================= 11 tests collected in 0.12s ========================
+   collected 12 items
+
+   <Dir fixtures.rst-227>
+     <Module test_anothersmtp.py>
+       <Function test_showhelo[smtp.gmail.com]>
+       <Function test_showhelo[mail.python.org]>
+     <Module test_emaillib.py>
+       <Function test_email_received>
+     <Module test_finalizers.py>
+       <Function test_bar>
+     <Module test_ids.py>
+       <Function test_a[spam]>
+       <Function test_a[ham]>
+       <Function test_b[eggs]>
+       <Function test_b[1]>
+     <Module test_module.py>
+       <Function test_ehlo[smtp.gmail.com]>
+       <Function test_noop[smtp.gmail.com]>
+       <Function test_ehlo[mail.python.org]>
+       <Function test_noop[mail.python.org]>
+
+   ======================= 12 tests collected in 0.12s ========================
 
 .. _`fixture-parametrize-marks`:
 
@@ -1384,7 +1469,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
 
     $ pytest test_fixture_marks.py -v
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 3 items
@@ -1402,7 +1487,7 @@ Modularity: using fixtures from a fixture function
 
 In addition to using fixtures in test functions, fixture functions
 can use other fixtures themselves.  This contributes to a modular design
-of your fixtures and allows re-use of framework-specific fixtures across
+of your fixtures and allows reuse of framework-specific fixtures across
 many projects.  As a simple example, we can extend the previous example
 and instantiate an object ``app`` where we stick the already defined
 ``smtp_connection`` resource into it:
@@ -1434,7 +1519,7 @@ Here we declare an ``app`` fixture which receives the previously defined
 
     $ pytest -v test_appsetup.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 2 items
@@ -1505,7 +1590,7 @@ to show the setup/teardown flow:
 
 
     def test_2(otherarg, modarg):
-        print("  RUN test2 with otherarg {} and modarg {}".format(otherarg, modarg))
+        print(f"  RUN test2 with otherarg {otherarg} and modarg {modarg}")
 
 
 Let's run the tests in verbose mode and with looking at the print-output:
@@ -1514,7 +1599,7 @@ Let's run the tests in verbose mode and with looking at the print-output:
 
     $ pytest -v -s test_module.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collecting ... collected 8 items
@@ -1606,6 +1691,7 @@ and declare its use in a test module via a ``usefixtures`` marker:
 
     # content of test_setenv.py
     import os
+
     import pytest
 
 
@@ -1613,7 +1699,7 @@ and declare its use in a test module via a ``usefixtures`` marker:
     class TestDirectoryInit:
         def test_cwd_starts_empty(self):
             assert os.listdir(os.getcwd()) == []
-            with open("myfile", "w") as f:
+            with open("myfile", "w", encoding="utf-8") as f:
                 f.write("hello")
 
         def test_cwd_again_starts_empty(self):
@@ -1635,8 +1721,7 @@ You can specify multiple fixtures like this:
 .. code-block:: python
 
     @pytest.mark.usefixtures("cleandir", "anotherfixture")
-    def test():
-        ...
+    def test(): ...
 
 and you may specify fixture usage at the test module level using :globalvar:`pytestmark`:
 
@@ -1664,11 +1749,9 @@ into an ini-file:
 
         @pytest.mark.usefixtures("my_other_fixture")
         @pytest.fixture
-        def my_fixture_that_sadly_wont_use_my_other_fixture():
-            ...
+        def my_fixture_that_sadly_wont_use_my_other_fixture(): ...
 
-    Currently this will not generate any error or warning, but this is intended
-    to be handled by :issue:`3664`.
+    This generates a deprecation warning, and will become an error in Pytest 8.
 
 .. _`override fixtures`:
 
@@ -1686,8 +1769,6 @@ Given the tests file structure is:
 ::
 
     tests/
-        __init__.py
-
         conftest.py
             # content of tests/conftest.py
             import pytest
@@ -1702,8 +1783,6 @@ Given the tests file structure is:
                 assert username == 'username'
 
         subfolder/
-            __init__.py
-
             conftest.py
                 # content of tests/subfolder/conftest.py
                 import pytest
@@ -1712,8 +1791,8 @@ Given the tests file structure is:
                 def username(username):
                     return 'overridden-' + username
 
-            test_something.py
-                # content of tests/subfolder/test_something.py
+            test_something_else.py
+                # content of tests/subfolder/test_something_else.py
                 def test_username(username):
                     assert username == 'overridden-username'
 
@@ -1729,8 +1808,6 @@ Given the tests file structure is:
 ::
 
     tests/
-        __init__.py
-
         conftest.py
             # content of tests/conftest.py
             import pytest
@@ -1772,8 +1849,6 @@ Given the tests file structure is:
 ::
 
     tests/
-        __init__.py
-
         conftest.py
             # content of tests/conftest.py
             import pytest
@@ -1810,8 +1885,6 @@ Given the tests file structure is:
 ::
 
     tests/
-        __init__.py
-
         conftest.py
             # content of tests/conftest.py
             import pytest
@@ -1858,7 +1931,7 @@ The same applies for the test folder level obviously.
 Using fixtures from other projects
 ----------------------------------
 
-Usually projects that provide pytest support will use :ref:`entry points <setuptools entry points>`,
+Usually projects that provide pytest support will use :ref:`entry points <pip-installable plugins>`,
 so just installing those projects into an environment will make those fixtures available for use.
 
 In case you want to use fixtures from a project that does not use entry points, you can
diff --git a/doc/en/how-to/index.rst b/doc/en/how-to/index.rst
index 6f52aaecdc3..225f289651e 100644
--- a/doc/en/how-to/index.rst
+++ b/doc/en/how-to/index.rst
@@ -52,7 +52,6 @@ pytest and other test systems
 
    existingtestsuite
    unittest
-   nose
    xunit_setup
 
 pytest development environment
diff --git a/doc/en/how-to/logging.rst b/doc/en/how-to/logging.rst
index 2e8734fa6a3..300e9f6e6c2 100644
--- a/doc/en/how-to/logging.rst
+++ b/doc/en/how-to/logging.rst
@@ -55,6 +55,13 @@ These options can also be customized through ``pytest.ini`` file:
     log_format = %(asctime)s %(levelname)s %(message)s
     log_date_format = %Y-%m-%d %H:%M:%S
 
+Specific loggers can be disabled via ``--log-disable={logger_name}``.
+This argument can be passed multiple times:
+
+.. code-block:: bash
+
+    pytest --log-disable=main --log-disable=testing
+
 Further it is possible to disable reporting of captured content (stdout,
 stderr and logs) on failed tests completely with:
 
@@ -73,7 +80,6 @@ messages.  This is supported by the ``caplog`` fixture:
 
     def test_foo(caplog):
         caplog.set_level(logging.INFO)
-        pass
 
 By default the level is set on the root logger,
 however as a convenience it is also possible to set the log level of any
@@ -83,7 +89,6 @@ logger:
 
     def test_foo(caplog):
         caplog.set_level(logging.CRITICAL, logger="root.baz")
-        pass
 
 The log levels set are restored automatically at the end of the test.
 
@@ -161,14 +166,19 @@ the records for the ``setup`` and ``call`` stages during teardown like so:
                 x.message for x in caplog.get_records(when) if x.levelno == logging.WARNING
             ]
             if messages:
-                pytest.fail(
-                    "warning messages encountered during testing: {}".format(messages)
-                )
+                pytest.fail(f"warning messages encountered during testing: {messages}")
 
 
 
 The full API is available at :class:`pytest.LogCaptureFixture`.
 
+.. warning::
+
+    The ``caplog`` fixture adds a handler to the root logger to capture logs. If the root logger is
+    modified during a test, for example with ``logging.config.dictConfig``, this handler may be
+    removed and cause no logs to be captured. To avoid this, ensure that any root logger configuration
+    only adds to the existing handlers.
+
 
 .. _live_logs:
 
@@ -180,8 +190,8 @@ logging records as they are emitted directly into the console.
 
 You can specify the logging level for which log records with equal or higher
 level are printed to the console by passing ``--log-cli-level``. This setting
-accepts the logging level names as seen in python's documentation or an integer
-as the logging level num.
+accepts the logging level names or numeric values as seen in
+:ref:`logging's documentation <python:levels>`.
 
 Additionally, you can also specify ``--log-cli-format`` and
 ``--log-cli-date-format`` which mirror and default to ``--log-format`` and
@@ -196,13 +206,15 @@ option names are:
 * ``log_cli_date_format``
 
 If you need to record the whole test suite logging calls to a file, you can pass
-``--log-file=/path/to/log/file``. This log file is opened in write mode which
+``--log-file=/path/to/log/file``. This log file is opened in write mode by default which
 means that it will be overwritten at each run tests session.
+If you'd like the file opened in append mode instead, then you can pass ``--log-file-mode=a``.
+Note that relative paths for the log-file location, whether passed on the CLI or declared in a
+config file, are always resolved relative to the current working directory.
 
 You can also specify the logging level for the log file by passing
-``--log-file-level``. This setting accepts the logging level names as seen in
-python's documentation(ie, uppercased level names) or an integer as the logging
-level num.
+``--log-file-level``. This setting accepts the logging level names or numeric
+values as seen in :ref:`logging's documentation <python:levels>`.
 
 Additionally, you can also specify ``--log-file-format`` and
 ``--log-file-date-format`` which are equal to ``--log-format`` and
@@ -212,12 +224,13 @@ All of the log file options can also be set in the configuration INI file. The
 option names are:
 
 * ``log_file``
+* ``log_file_mode``
 * ``log_file_level``
 * ``log_file_format``
 * ``log_file_date_format``
 
 You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality
-is considered **experimental**.
+is considered **experimental**. Note that ``set_log_path()`` respects the ``log_file_mode`` option.
 
 .. _log_colors:
 
@@ -230,7 +243,7 @@ through ``add_color_level()``. Example:
 
 .. code-block:: python
 
-    @pytest.hookimpl
+    @pytest.hookimpl(trylast=True)
     def pytest_configure(config):
         logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
 
diff --git a/doc/en/how-to/monkeypatch.rst b/doc/en/how-to/monkeypatch.rst
index 9c61233f7e5..a9504dcb32a 100644
--- a/doc/en/how-to/monkeypatch.rst
+++ b/doc/en/how-to/monkeypatch.rst
@@ -3,7 +3,7 @@
 How to monkeypatch/mock modules and environments
 ================================================================
 
-.. currentmodule:: _pytest.monkeypatch
+.. currentmodule:: pytest
 
 Sometimes tests need to invoke functionality which depends
 on global settings or which invokes code which cannot be easily
@@ -14,17 +14,16 @@ environment variable, or to modify ``sys.path`` for importing.
 The ``monkeypatch`` fixture provides these helper methods for safely patching and mocking
 functionality in tests:
 
-.. code-block:: python
+* :meth:`monkeypatch.setattr(obj, name, value, raising=True) <pytest.MonkeyPatch.setattr>`
+* :meth:`monkeypatch.delattr(obj, name, raising=True) <pytest.MonkeyPatch.delattr>`
+* :meth:`monkeypatch.setitem(mapping, name, value) <pytest.MonkeyPatch.setitem>`
+* :meth:`monkeypatch.delitem(obj, name, raising=True) <pytest.MonkeyPatch.delitem>`
+* :meth:`monkeypatch.setenv(name, value, prepend=None) <pytest.MonkeyPatch.setenv>`
+* :meth:`monkeypatch.delenv(name, raising=True) <pytest.MonkeyPatch.delenv>`
+* :meth:`monkeypatch.syspath_prepend(path) <pytest.MonkeyPatch.syspath_prepend>`
+* :meth:`monkeypatch.chdir(path) <pytest.MonkeyPatch.chdir>`
+* :meth:`monkeypatch.context() <pytest.MonkeyPatch.context>`
 
-    monkeypatch.setattr(obj, name, value, raising=True)
-    monkeypatch.setattr("somemodule.obj.name", value, raising=True)
-    monkeypatch.delattr(obj, name, raising=True)
-    monkeypatch.setitem(mapping, name, value)
-    monkeypatch.delitem(obj, name, raising=True)
-    monkeypatch.setenv(name, value, prepend=None)
-    monkeypatch.delenv(name, raising=True)
-    monkeypatch.syspath_prepend(path)
-    monkeypatch.chdir(path)
 
 All modifications will be undone after the requesting
 test function or fixture has finished. The ``raising``
@@ -55,13 +54,16 @@ during a test.
 5. Use :py:meth:`monkeypatch.syspath_prepend <MonkeyPatch.syspath_prepend>` to modify ``sys.path`` which will also
 call ``pkg_resources.fixup_namespace_packages`` and :py:func:`importlib.invalidate_caches`.
 
+6. Use :py:meth:`monkeypatch.context <MonkeyPatch.context>` to apply patches only in a specific scope, which can help
+control teardown of complex fixtures or patches to the stdlib.
+
 See the `monkeypatch blog post`_ for some introduction material
 and a discussion of its motivation.
 
 .. _`monkeypatch blog post`: https://tetamap.wordpress.com//2009/03/03/monkeypatching-in-unit-tests-done-right/
 
-Simple example: monkeypatching functions
-----------------------------------------
+Monkeypatching functions
+------------------------
 
 Consider a scenario where you are working with user directories. In the context of
 testing, you do not want your test to depend on the running user. ``monkeypatch``
@@ -133,10 +135,10 @@ This can be done in our test file by defining a class to represent ``r``.
     # this is the previous code block example
     import app
 
+
     # custom class to be the mock return value
     # will override the requests.Response returned from requests.get
     class MockResponse:
-
         # mock json() method always returns a specific testing dictionary
         @staticmethod
         def json():
@@ -144,7 +146,6 @@ This can be done in our test file by defining a class to represent ``r``.
 
 
     def test_get_json(monkeypatch):
-
         # Any arguments may be passed and mock_get() will always return our
         # mocked object, which only has the .json() method.
         def mock_get(*args, **kwargs):
@@ -179,6 +180,7 @@ This mock can be shared across tests using a ``fixture``:
     # app.py that includes the get_json() function
     import app
 
+
     # custom class to be the mock return value of requests.get()
     class MockResponse:
         @staticmethod
@@ -356,7 +358,6 @@ For testing purposes we can patch the ``DEFAULT_CONFIG`` dictionary to specific
 
 
     def test_connection(monkeypatch):
-
         # Patch the values of DEFAULT_CONFIG to specific
         # testing values only for this test.
         monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user")
@@ -381,7 +382,6 @@ You can use the :py:meth:`monkeypatch.delitem <MonkeyPatch.delitem>` to remove v
 
 
     def test_missing_user(monkeypatch):
-
         # patch the DEFAULT_CONFIG t be missing the 'user' key
         monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False)
 
@@ -402,6 +402,7 @@ separate fixtures for each potential mock and reference them in the needed tests
     # app.py with the connection string function
     import app
 
+
     # all of the mocks are moved into separated fixtures
     @pytest.fixture
     def mock_test_user(monkeypatch):
@@ -423,7 +424,6 @@ separate fixtures for each potential mock and reference them in the needed tests
 
     # tests reference only the fixture mocks that are needed
     def test_connection(mock_test_user, mock_test_database):
-
         expected = "User Id=test_user; Location=test_db;"
 
         result = app.create_connection_string()
@@ -431,12 +431,11 @@ separate fixtures for each potential mock and reference them in the needed tests
 
 
     def test_missing_user(mock_missing_default_user):
-
         with pytest.raises(KeyError):
             _ = app.create_connection_string()
 
 
-.. currentmodule:: _pytest.monkeypatch
+.. currentmodule:: pytest
 
 API Reference
 -------------
diff --git a/doc/en/how-to/nose.rst b/doc/en/how-to/nose.rst
deleted file mode 100644
index 4bf8b06c324..00000000000
--- a/doc/en/how-to/nose.rst
+++ /dev/null
@@ -1,79 +0,0 @@
-.. _`noseintegration`:
-
-How to run tests written for nose
-=======================================
-
-``pytest`` has basic support for running tests written for nose_.
-
-.. _nosestyle:
-
-Usage
--------------
-
-After :ref:`installation` type:
-
-.. code-block:: bash
-
-    python setup.py develop  # make sure tests can import our package
-    pytest  # instead of 'nosetests'
-
-and you should be able to run your nose style tests and
-make use of pytest's capabilities.
-
-Supported nose Idioms
-----------------------
-
-* setup and teardown at module/class/method level
-* SkipTest exceptions and markers
-* setup/teardown decorators
-* ``__test__`` attribute on modules/classes/functions
-* general usage of nose utilities
-
-Unsupported idioms / known issues
-----------------------------------
-
-- unittest-style ``setUp, tearDown, setUpClass, tearDownClass``
-  are recognized only on ``unittest.TestCase`` classes but not
-  on plain classes.  ``nose`` supports these methods also on plain
-  classes but pytest deliberately does not.  As nose and pytest already
-  both support ``setup_class, teardown_class, setup_method, teardown_method``
-  it doesn't seem useful to duplicate the unittest-API like nose does.
-  If you however rather think pytest should support the unittest-spelling on
-  plain classes please post to :issue:`377`.
-
-- nose imports test modules with the same import path (e.g.
-  ``tests.test_mode``) but different file system paths
-  (e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``)
-  by extending sys.path/import semantics.   pytest does not do that
-  but there is discussion in :issue:`268` for adding some support.  Note that
-  `nose2 choose to avoid this sys.path/import hackery <https://nose2.readthedocs.io/en/latest/differences.html#test-discovery-and-loading>`_.
-
-  If you place a conftest.py file in the root directory of your project
-  (as determined by pytest) pytest will run tests "nose style" against
-  the code below that directory by adding it to your ``sys.path`` instead of
-  running against your installed code.
-
-  You may find yourself wanting to do this if you ran ``python setup.py install``
-  to set up your project, as opposed to ``python setup.py develop`` or any of
-  the package manager equivalents.  Installing with develop in a
-  virtual environment like tox is recommended over this pattern.
-
-- nose-style doctests are not collected and executed correctly,
-  also doctest fixtures don't work.
-
-- no nose-configuration is recognized.
-
-- ``yield``-based methods are unsupported as of pytest 4.1.0.  They are
-  fundamentally incompatible with pytest because they don't support fixtures
-  properly since collection and test execution are separated.
-
-Migrating from nose to pytest
-------------------------------
-
-`nose2pytest <https://github.com/pytest-dev/nose2pytest>`_ is a Python script
-and pytest plugin to help convert Nose-based tests into pytest-based tests.
-Specifically, the script transforms nose.tools.assert_* function calls into
-raw assert statements, while preserving format of original arguments
-as much as possible.
-
-.. _nose: https://nose.readthedocs.io/en/latest/
diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst
index 38de25edc7b..d53dd4b8ec7 100644
--- a/doc/en/how-to/output.rst
+++ b/doc/en/how-to/output.rst
@@ -12,8 +12,15 @@ Examples for modifying traceback printing:
 
 .. code-block:: bash
 
-    pytest --showlocals # show local variables in tracebacks
-    pytest -l           # show local variables (shortcut)
+    pytest --showlocals     # show local variables in tracebacks
+    pytest -l               # show local variables (shortcut)
+    pytest --no-showlocals  # hide local variables (if addopts enables them)
+
+    pytest --capture=fd  # default, capture at the file descriptor level
+    pytest --capture=sys # capture at the sys level
+    pytest --capture=no  # don't capture
+    pytest -s            # don't capture (shortcut)
+    pytest --capture=tee-sys # capture to logs but also output to sys level streams
 
     pytest --tb=auto    # (default) 'long' tracebacks for the first and last
                          # entry, but 'short' style for the other entries
@@ -35,6 +42,16 @@ option you make sure a trace is shown.
 Verbosity
 --------------------------------------------------
 
+Examples for modifying printing verbosity:
+
+.. code-block:: bash
+
+    pytest --quiet          # quiet - less verbose - mode
+    pytest -q               # quiet - less verbose - mode (shortcut)
+    pytest -v               # increase verbosity, display individual test names
+    pytest -vv              # more verbose, display more details from the test output
+    pytest -vvv             # not a standard , but may be used for even more detail in certain setups
+
 The ``-v`` flag controls the verbosity of pytest output in various aspects: test session progress, assertion
 details when tests fail, fixtures details with ``--fixtures``, etc.
 
@@ -83,8 +100,9 @@ Executing pytest normally gives us this output (we are skipping the header to fo
             fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
     >       assert fruits1 == fruits2
     E       AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
+    E
     E         At index 2 diff: 'grapes' != 'orange'
-    E         Use -v to get the full diff
+    E         Use -v to get more diff
 
     test_verbosity_example.py:8: AssertionError
     ____________________________ test_numbers_fail _____________________________
@@ -94,12 +112,13 @@ Executing pytest normally gives us this output (we are skipping the header to fo
             number_to_text2 = {str(x * 10): x * 10 for x in range(5)}
     >       assert number_to_text1 == number_to_text2
     E       AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...}
+    E
     E         Omitting 1 identical items, use -vv to show
     E         Left contains 4 more items:
     E         {'1': 1, '2': 2, '3': 3, '4': 4}
     E         Right contains 4 more items:
     E         {'10': 10, '20': 20, '30': 30, '40': 40}
-    E         Use -v to get the full diff
+    E         Use -v to get more diff
 
     test_verbosity_example.py:14: AssertionError
     ___________________________ test_long_text_fail ____________________________
@@ -145,12 +164,15 @@ Now we can increase pytest's verbosity:
             fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
     >       assert fruits1 == fruits2
     E       AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
+    E
     E         At index 2 diff: 'grapes' != 'orange'
+    E
     E         Full diff:
-    E         - ['banana', 'apple', 'orange', 'melon', 'kiwi']
-    E         ?                      ^  ^^
-    E         + ['banana', 'apple', 'grapes', 'melon', 'kiwi']
-    E         ?                      ^  ^ +
+    E           [
+    E               'banana',
+    E               'apple',...
+    E
+    E         ...Full output truncated (7 lines hidden), use '-vv' to show
 
     test_verbosity_example.py:8: AssertionError
     ____________________________ test_numbers_fail _____________________________
@@ -160,15 +182,15 @@ Now we can increase pytest's verbosity:
             number_to_text2 = {str(x * 10): x * 10 for x in range(5)}
     >       assert number_to_text1 == number_to_text2
     E       AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...}
+    E
     E         Omitting 1 identical items, use -vv to show
     E         Left contains 4 more items:
     E         {'1': 1, '2': 2, '3': 3, '4': 4}
     E         Right contains 4 more items:
     E         {'10': 10, '20': 20, '30': 30, '40': 40}
-    E         Full diff:
-    E         - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}...
+    E         ...
     E
-    E         ...Full output truncated (3 lines hidden), use '-vv' to show
+    E         ...Full output truncated (16 lines hidden), use '-vv' to show
 
     test_verbosity_example.py:14: AssertionError
     ___________________________ test_long_text_fail ____________________________
@@ -214,12 +236,20 @@ Now if we increase verbosity even more:
             fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
     >       assert fruits1 == fruits2
     E       AssertionError: assert ['banana', 'apple', 'grapes', 'melon', 'kiwi'] == ['banana', 'apple', 'orange', 'melon', 'kiwi']
+    E
     E         At index 2 diff: 'grapes' != 'orange'
+    E
     E         Full diff:
-    E         - ['banana', 'apple', 'orange', 'melon', 'kiwi']
-    E         ?                      ^  ^^
-    E         + ['banana', 'apple', 'grapes', 'melon', 'kiwi']
-    E         ?                      ^  ^ +
+    E           [
+    E               'banana',
+    E               'apple',
+    E         -     'orange',
+    E         ?      ^  ^^
+    E         +     'grapes',
+    E         ?      ^  ^ +
+    E               'melon',
+    E               'kiwi',
+    E           ]
 
     test_verbosity_example.py:8: AssertionError
     ____________________________ test_numbers_fail _____________________________
@@ -229,16 +259,30 @@ Now if we increase verbosity even more:
             number_to_text2 = {str(x * 10): x * 10 for x in range(5)}
     >       assert number_to_text1 == number_to_text2
     E       AssertionError: assert {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} == {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}
+    E
     E         Common items:
     E         {'0': 0}
     E         Left contains 4 more items:
     E         {'1': 1, '2': 2, '3': 3, '4': 4}
     E         Right contains 4 more items:
     E         {'10': 10, '20': 20, '30': 30, '40': 40}
+    E
     E         Full diff:
-    E         - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}
-    E         ?            -    -    -    -    -    -    -    -
-    E         + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}
+    E           {
+    E               '0': 0,
+    E         -     '10': 10,
+    E         ?       -    -
+    E         +     '1': 1,
+    E         -     '20': 20,
+    E         ?       -    -
+    E         +     '2': 2,
+    E         -     '30': 30,
+    E         ?       -    -
+    E         +     '3': 3,
+    E         -     '40': 40,
+    E         ?       -    -
+    E         +     '4': 4,
+    E           }
 
     test_verbosity_example.py:14: AssertionError
     ___________________________ test_long_text_fail ____________________________
@@ -250,9 +294,47 @@ Now if we increase verbosity even more:
 
     test_verbosity_example.py:19: AssertionError
     ========================= short test summary info ==========================
-    FAILED test_verbosity_example.py::test_words_fail - AssertionError: asser...
-    FAILED test_verbosity_example.py::test_numbers_fail - AssertionError: ass...
-    FAILED test_verbosity_example.py::test_long_text_fail - AssertionError: a...
+    FAILED test_verbosity_example.py::test_words_fail - AssertionError: assert ['banana', 'apple', 'grapes', 'melon', 'kiwi'] == ['banana', 'apple', 'orange', 'melon', 'kiwi']
+
+      At index 2 diff: 'grapes' != 'orange'
+
+      Full diff:
+        [
+            'banana',
+            'apple',
+      -     'orange',
+      ?      ^  ^^
+      +     'grapes',
+      ?      ^  ^ +
+            'melon',
+            'kiwi',
+        ]
+    FAILED test_verbosity_example.py::test_numbers_fail - AssertionError: assert {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} == {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}
+
+      Common items:
+      {'0': 0}
+      Left contains 4 more items:
+      {'1': 1, '2': 2, '3': 3, '4': 4}
+      Right contains 4 more items:
+      {'10': 10, '20': 20, '30': 30, '40': 40}
+
+      Full diff:
+        {
+            '0': 0,
+      -     '10': 10,
+      ?       -    -
+      +     '1': 1,
+      -     '20': 20,
+      ?       -    -
+      +     '2': 2,
+      -     '30': 30,
+      ?       -    -
+      +     '3': 3,
+      -     '40': 40,
+      ?       -    -
+      +     '4': 4,
+        }
+    FAILED test_verbosity_example.py::test_long_text_fail - AssertionError: assert 'hello world' in 'Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet '
     ======================= 3 failed, 1 passed in 0.12s ========================
 
 Notice now that:
@@ -269,6 +351,22 @@ situations, for example you are shown even fixtures that start with ``_`` if you
 Using higher verbosity levels (``-vvv``, ``-vvvv``, ...) is supported, but has no effect in pytest itself at the moment,
 however some plugins might make use of higher verbosity.
 
+.. _`pytest.fine_grained_verbosity`:
+
+Fine-grained verbosity
+~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to specifying the application wide verbosity level, it is possible to control specific aspects independently.
+This is done by setting a verbosity level in the configuration file for the specific aspect of the output.
+
+:confval:`verbosity_assertions`: Controls how verbose the assertion output should be when pytest is executed. Running
+``pytest --no-header`` with a value of ``2`` would have the same output as the previous example, but each test inside
+the file is shown by a single character in the output.
+
+:confval:`verbosity_test_cases`: Controls how verbose the test execution output should be when pytest is executed.
+Running ``pytest --no-header`` with a value of ``2`` would have the same output as the first verbosity example, but each
+test inside the file gets its own line in the output.
+
 .. _`pytest.detailed_failed_tests_usage`:
 
 Producing a detailed summary report
@@ -323,8 +421,7 @@ Example:
 
     $ pytest -ra
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 6 items
 
@@ -347,11 +444,11 @@ Example:
     E       assert 0
 
     test_example.py:14: AssertionError
+    ================================= XPASSES ==================================
     ========================= short test summary info ==========================
     SKIPPED [1] test_example.py:22: skipping this test
-    XFAIL test_example.py::test_xfail
-      reason: xfailing this test
-    XPASS test_example.py::test_xpass always xfail
+    XFAIL test_example.py::test_xfail - reason: xfailing this test
+    XPASS test_example.py::test_xpass - always xfail
     ERROR test_example.py::test_error - assert 0
     FAILED test_example.py::test_fail - assert 0
     == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
@@ -381,8 +478,7 @@ More than one character can be used, so for example to only see failed and skipp
 
     $ pytest -rfs
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 6 items
 
@@ -417,8 +513,7 @@ captured output:
 
     $ pytest -rpP
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 6 items
 
@@ -449,6 +544,33 @@ captured output:
     PASSED test_example.py::test_ok
     == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
 
+.. note::
+
+    By default, parametrized variants of skipped tests are grouped together if
+    they share the same skip reason. You can use ``--no-fold-skipped`` to print each skipped test separately.
+
+
+.. _truncation-params:
+
+Modifying truncation limits
+--------------------------------------------------
+
+.. versionadded: 8.4
+
+Default truncation limits are 8 lines or 640 characters, whichever comes first.
+To set custom truncation limits you can use following ``pytest.ini`` file options:
+
+.. code-block:: ini
+
+    [pytest]
+    truncation_limit_lines = 10
+    truncation_limit_chars = 90
+
+That will cause pytest to truncate the assertions to 10 lines or 90 characters, whichever comes first.
+
+Setting both :confval:`truncation_limit_lines` and :confval:`truncation_limit_chars` to ``0`` will disable the truncation.
+However, setting only one of those values will disable one truncation mode, but will leave the other one intact.
+
 Creating resultlog format files
 --------------------------------------------------
 
@@ -481,7 +603,7 @@ integration servers, use this invocation:
 
 .. code-block:: bash
 
-    pytest --junitxml=path
+    pytest --junit-xml=path
 
 to create an XML file at ``path``.
 
diff --git a/doc/en/how-to/parametrize.rst b/doc/en/how-to/parametrize.rst
index 240016601be..5a16684eb96 100644
--- a/doc/en/how-to/parametrize.rst
+++ b/doc/en/how-to/parametrize.rst
@@ -29,10 +29,6 @@ pytest enables test parametrization at several levels:
 
 .. regendoc: wipe
 
-
-
-    Several improvements.
-
 The builtin :ref:`pytest.mark.parametrize ref` decorator enables
 parametrization of arguments for a test function.  Here is a typical example
 of a test function that implements checking that a certain input leads
@@ -56,8 +52,7 @@ them in turn:
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 3 items
 
@@ -168,8 +163,7 @@ Let's run this:
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 3 items
 
diff --git a/doc/en/how-to/plugins.rst b/doc/en/how-to/plugins.rst
index cae737e96ed..fca8ab54e63 100644
--- a/doc/en/how-to/plugins.rst
+++ b/doc/en/how-to/plugins.rst
@@ -21,7 +21,7 @@ there is no need to activate it.
 Here is a little annotated list for some popular plugins:
 
 * :pypi:`pytest-django`: write tests
-  for :std:doc:`django <django:index>` apps, using pytest integration.
+  for `django <https://docs.djangoproject.com/>`_ apps, using pytest integration.
 
 * :pypi:`pytest-twisted`: write tests
   for `twisted <https://twistedmatrix.com/>`_ apps, starting a reactor and
@@ -51,8 +51,8 @@ Here is a little annotated list for some popular plugins:
 * :pypi:`pytest-flakes`:
   check source code with pyflakes.
 
-* :pypi:`oejskit`:
-  a plugin to run javascript unittests in live browsers.
+* :pypi:`allure-pytest`:
+  report test results via `allure-framework <https://github.com/allure-framework/>`_.
 
 To see a complete list of all plugins with their latest testing
 status against different pytest and Python versions, please visit
@@ -133,4 +133,29 @@ CI server), you can set ``PYTEST_ADDOPTS`` environment variable to
 
 See :ref:`findpluginname` for how to obtain the name of a plugin.
 
-.. _`builtin plugins`:
+.. _`disable_plugin_autoload`:
+
+Disabling plugins from autoloading
+----------------------------------
+
+If you want to disable plugins from loading automatically, instead of requiring you to
+manually specify each plugin with ``-p`` or :envvar:`PYTEST_PLUGINS`, you can use ``--disable-plugin-autoload`` or :envvar:`PYTEST_DISABLE_PLUGIN_AUTOLOAD`.
+
+.. code-block:: bash
+
+   export PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
+   export PYTEST_PLUGINS=NAME,NAME2
+   pytest
+
+.. code-block:: bash
+
+   pytest --disable-plugin-autoload -p NAME,NAME2
+
+.. code-block:: ini
+
+    [pytest]
+    addopts = --disable-plugin-autoload -p NAME,NAME2
+
+.. versionadded:: 8.4
+
+   The ``--disable-plugin-autoload`` command-line flag.
diff --git a/doc/en/how-to/skipping.rst b/doc/en/how-to/skipping.rst
index 9b74628d59f..09a19766f99 100644
--- a/doc/en/how-to/skipping.rst
+++ b/doc/en/how-to/skipping.rst
@@ -47,8 +47,7 @@ which may be passed an optional ``reason``:
 .. code-block:: python
 
     @pytest.mark.skip(reason="no way of currently testing this")
-    def test_the_unknown():
-        ...
+    def test_the_unknown(): ...
 
 
 Alternatively, it is also possible to skip imperatively during test execution or setup
@@ -69,6 +68,7 @@ It is also possible to skip the whole module using
 .. code-block:: python
 
     import sys
+
     import pytest
 
     if not sys.platform.startswith("win"):
@@ -84,16 +84,15 @@ It is also possible to skip the whole module using
 
 If you wish to skip something conditionally then you can use ``skipif`` instead.
 Here is an example of marking a test function to be skipped
-when run on an interpreter earlier than Python3.6:
+when run on an interpreter earlier than Python3.10:
 
 .. code-block:: python
 
     import sys
 
 
-    @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires python3.7 or higher")
-    def test_function():
-        ...
+    @pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
+    def test_function(): ...
 
 If the condition evaluates to ``True`` during collection, the test function will be skipped,
 with the specified reason appearing in the summary when using ``-rs``.
@@ -111,8 +110,7 @@ You can share ``skipif`` markers between modules.  Consider this test module:
 
 
     @minversion
-    def test_function():
-        ...
+    def test_function(): ...
 
 You can import the marker and reuse it in another test module:
 
@@ -123,8 +121,7 @@ You can import the marker and reuse it in another test module:
 
 
     @minversion
-    def test_anotherfunction():
-        ...
+    def test_anotherfunction(): ...
 
 For larger test suites it's usually a good idea to have one file
 where you define the markers which you then consistently apply
@@ -231,8 +228,7 @@ expect a test to fail:
 .. code-block:: python
 
     @pytest.mark.xfail
-    def test_function():
-        ...
+    def test_function(): ...
 
 This test will run but no traceback will be reported when it fails. Instead, terminal
 reporting will list it in the "expected to fail" (``XFAIL``) or "unexpectedly
@@ -274,8 +270,7 @@ that condition as the first parameter:
 .. code-block:: python
 
     @pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library")
-    def test_function():
-        ...
+    def test_function(): ...
 
 Note that you have to pass a reason as well (see the parameter description at
 :ref:`pytest.mark.xfail ref`).
@@ -288,8 +283,7 @@ You can specify the motive of an expected failure with the ``reason`` parameter:
 .. code-block:: python
 
     @pytest.mark.xfail(reason="known parser issue")
-    def test_function():
-        ...
+    def test_function(): ...
 
 
 ``raises`` parameter
@@ -301,8 +295,7 @@ a single exception, or a tuple of exceptions, in the ``raises`` argument.
 .. code-block:: python
 
     @pytest.mark.xfail(raises=RuntimeError)
-    def test_function():
-        ...
+    def test_function(): ...
 
 Then the test will be reported as a regular failure if it fails with an
 exception not mentioned in ``raises``.
@@ -316,8 +309,7 @@ even executed, use the ``run`` parameter as ``False``:
 .. code-block:: python
 
     @pytest.mark.xfail(run=False)
-    def test_function():
-        ...
+    def test_function(): ...
 
 This is specially useful for xfailing tests that are crashing the interpreter and should be
 investigated later.
@@ -333,8 +325,7 @@ You can change this by setting the ``strict`` keyword-only parameter to ``True``
 .. code-block:: python
 
     @pytest.mark.xfail(strict=True)
-    def test_function():
-        ...
+    def test_function(): ...
 
 
 This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite.
@@ -369,7 +360,7 @@ Here is a simple test file with the several usages:
 
 Running it with the report-on-xfail option gives this output:
 
-.. FIXME: Use $ instead of ! again to reenable regendoc once it's fixed:
+.. FIXME: Use $ instead of ! again to re-enable regendoc once it's fixed:
    https://github.com/pytest-dev/pytest/issues/8807
 
 .. code-block:: pytest
@@ -409,6 +400,7 @@ test instances when using parametrize:
 .. code-block:: python
 
     import sys
+
     import pytest
 
 
diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst
index 3e95116dca9..d19950431e5 100644
--- a/doc/en/how-to/tmp_path.rst
+++ b/doc/en/how-to/tmp_path.rst
@@ -8,9 +8,8 @@ How to use temporary directories and files in tests
 The ``tmp_path`` fixture
 ------------------------
 
-You can use the ``tmp_path`` fixture which will
-provide a temporary directory unique to the test invocation,
-created in the `base temporary directory`_.
+You can use the ``tmp_path`` fixture which will provide a temporary directory
+unique to each test function.
 
 ``tmp_path`` is a :class:`pathlib.Path` object. Here is an example test usage:
 
@@ -24,8 +23,8 @@ created in the `base temporary directory`_.
         d = tmp_path / "sub"
         d.mkdir()
         p = d / "hello.txt"
-        p.write_text(CONTENT)
-        assert p.read_text() == CONTENT
+        p.write_text(CONTENT, encoding="utf-8")
+        assert p.read_text(encoding="utf-8") == CONTENT
         assert len(list(tmp_path.iterdir())) == 1
         assert 0
 
@@ -36,8 +35,7 @@ Running this would result in a passed test except for the last
 
     $ pytest test_tmp_path.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 1 item
 
@@ -52,8 +50,8 @@ Running this would result in a passed test except for the last
             d = tmp_path / "sub"
             d.mkdir()
             p = d / "hello.txt"
-            p.write_text(CONTENT)
-            assert p.read_text() == CONTENT
+            p.write_text(CONTENT, encoding="utf-8")
+            assert p.read_text(encoding="utf-8") == CONTENT
             assert len(list(tmp_path.iterdir())) == 1
     >       assert 0
     E       assert 0
@@ -63,6 +61,11 @@ Running this would result in a passed test except for the last
     FAILED test_tmp_path.py::test_create_file - assert 0
     ============================ 1 failed in 0.12s =============================
 
+By default, ``pytest`` retains the temporary directory for the last 3 ``pytest``
+invocations. Concurrent invocations of the same test function are supported by
+configuring the base temporary directory to be unique for each concurrent
+run. See `temporary directory location and retention`_ for details.
+
 .. _`tmp_path_factory example`:
 
 The ``tmp_path_factory`` fixture
@@ -101,40 +104,79 @@ See :ref:`tmp_path_factory API <tmp_path_factory factory api>` for details.
 .. _tmpdir:
 
 The ``tmpdir`` and ``tmpdir_factory`` fixtures
----------------------------------------------------
+----------------------------------------------
 
 The ``tmpdir`` and ``tmpdir_factory`` fixtures are similar to ``tmp_path``
 and ``tmp_path_factory``, but use/return legacy `py.path.local`_ objects
-rather than standard :class:`pathlib.Path` objects. These days, prefer to
-use ``tmp_path`` and ``tmp_path_factory``.
+rather than standard :class:`pathlib.Path` objects.
+
+.. note::
+    These days, it is preferred to use ``tmp_path`` and ``tmp_path_factory``.
+
+    In order to help modernize old code bases, one can run pytest with the legacypath
+    plugin disabled:
+
+    .. code-block:: bash
+
+        pytest -p no:legacypath
+
+    This will trigger errors on tests using the legacy paths.
+    It can also be permanently set as part of the :confval:`addopts` parameter in the
+    config file.
 
 See :fixture:`tmpdir <tmpdir>` :fixture:`tmpdir_factory <tmpdir_factory>`
 API for details.
 
 
-.. _`base temporary directory`:
+.. _`temporary directory location and retention`:
+
+Temporary directory location and retention
+------------------------------------------
+
+The temporary directories,
+as returned by the :fixture:`tmp_path` and (now deprecated) :fixture:`tmpdir` fixtures,
+are automatically created under a base temporary directory,
+in a structure that depends on the ``--basetemp`` option:
+
+- By default (when the ``--basetemp`` option is not set),
+  the temporary directories will follow this template:
+
+  .. code-block:: text
+
+      {temproot}/pytest-of-{user}/pytest-{num}/{testname}/
+
+  where:
+
+  - ``{temproot}`` is the system temporary directory
+    as determined by :py:func:`tempfile.gettempdir`.
+    It can be overridden by the :envvar:`PYTEST_DEBUG_TEMPROOT` environment variable.
+  - ``{user}`` is the user name running the tests,
+  - ``{num}`` is a number that is incremented with each test suite run
+  - ``{testname}`` is a sanitized version of :py:attr:`the name of the current test <_pytest.nodes.Node.name>`.
 
-The default base temporary directory
------------------------------------------------
+  The auto-incrementing ``{num}`` placeholder provides a basic retention feature
+  and avoids that existing results of previous test runs are blindly removed.
+  By default, the last 3 temporary directories are kept,
+  but this behavior can be configured with
+  :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy`.
 
-Temporary directories are by default created as sub-directories of
-the system temporary directory.  The base name will be ``pytest-NUM`` where
-``NUM`` will be incremented with each test run.  Moreover, entries older
-than 3 temporary directories will be removed.
+- When the ``--basetemp`` option is used (e.g. ``pytest --basetemp=mydir``),
+  it will be used directly as base temporary directory:
 
-You can override the default temporary directory setting like this:
+  .. code-block:: text
 
-.. code-block:: bash
+      {basetemp}/{testname}/
 
-    pytest --basetemp=mydir
+  Note that there is no retention feature in this case:
+  only the results of the most recent run will be kept.
 
-.. warning::
+  .. warning::
 
-    The contents of ``mydir`` will be completely removed, so make sure to use a directory
-    for that purpose only.
+      The directory given to ``--basetemp`` will be cleared blindly before each test run,
+      so make sure to use a directory for that purpose only.
 
 When distributing tests on the local machine using ``pytest-xdist``, care is taken to
-automatically configure a basetemp directory for the sub processes such that all temporary
-data lands below a single per-test run basetemp directory.
+automatically configure a `basetemp` directory for the sub processes such that all temporary
+data lands below a single per-test run temporary directory.
 
 .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
diff --git a/doc/en/how-to/unittest.rst b/doc/en/how-to/unittest.rst
index e2a23a1a785..62e32b6d28f 100644
--- a/doc/en/how-to/unittest.rst
+++ b/doc/en/how-to/unittest.rst
@@ -27,12 +27,15 @@ Almost all ``unittest`` features are supported:
 * ``setUpClass/tearDownClass``;
 * ``setUpModule/tearDownModule``;
 
+.. _`pytest-subtests`: https://github.com/pytest-dev/pytest-subtests
 .. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol
 
+Additionally, :ref:`subtests <python:subtests>` are supported by the
+`pytest-subtests`_ plugin.
+
 Up to this point pytest does not have support for the following features:
 
 * `load_tests protocol`_;
-* :ref:`subtests <python:subtests>`;
 
 Benefits out of the box
 -----------------------
@@ -106,7 +109,7 @@ achieves this by receiving a special ``request`` object which gives
 access to :ref:`the requesting test context <request-context>` such
 as the ``cls`` attribute, denoting the class from which the fixture
 is used.  This architecture de-couples fixture writing from actual test
-code and allows re-use of the fixture by a minimal reference, the fixture
+code and allows reuse of the fixture by a minimal reference, the fixture
 name.  So let's write an actual ``unittest.TestCase`` class using our
 fixture definition:
 
@@ -115,6 +118,7 @@ fixture definition:
     # content of test_unittest_db.py
 
     import unittest
+
     import pytest
 
 
@@ -136,8 +140,7 @@ the ``self.db`` values in the traceback:
 
     $ pytest test_unittest_db.py
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 2 items
 
@@ -154,7 +157,7 @@ the ``self.db`` values in the traceback:
     E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
     E       assert 0
 
-    test_unittest_db.py:10: AssertionError
+    test_unittest_db.py:11: AssertionError
     ___________________________ MyTest.test_method2 ____________________________
 
     self = <test_unittest_db.MyTest testMethod=test_method2>
@@ -164,7 +167,7 @@ the ``self.db`` values in the traceback:
     E       AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
     E       assert 0
 
-    test_unittest_db.py:13: AssertionError
+    test_unittest_db.py:14: AssertionError
     ========================= short test summary info ==========================
     FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
     FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
@@ -195,19 +198,19 @@ creation of a per-test temporary directory:
 .. code-block:: python
 
     # content of test_unittest_cleandir.py
-    import os
-    import pytest
     import unittest
 
+    import pytest
+
 
     class MyTest(unittest.TestCase):
         @pytest.fixture(autouse=True)
         def initdir(self, tmp_path, monkeypatch):
             monkeypatch.chdir(tmp_path)  # change to pytest-provided temporary directory
-            tmp_path.joinpath("samplefile.ini").write_text("# testdata")
+            tmp_path.joinpath("samplefile.ini").write_text("# testdata", encoding="utf-8")
 
         def test_method(self):
-            with open("samplefile.ini") as f:
+            with open("samplefile.ini", encoding="utf-8") as f:
                 s = f.read()
             assert "testdata" in s
 
diff --git a/doc/en/how-to/usage.rst b/doc/en/how-to/usage.rst
index 3522b258dce..0e0a0310fd8 100644
--- a/doc/en/how-to/usage.rst
+++ b/doc/en/how-to/usage.rst
@@ -17,7 +17,8 @@ in the current directory and its subdirectories. More generally, pytest follows
 Specifying which tests to run
 ------------------------------
 
-Pytest supports several ways to run and select tests from the command-line.
+Pytest supports several ways to run and select tests from the command-line or from a file
+(see below for :ref:`reading arguments from file <args-from-file>`).
 
 **Run tests in a module**
 
@@ -35,39 +36,59 @@ Pytest supports several ways to run and select tests from the command-line.
 
 .. code-block:: bash
 
-    pytest -k "MyClass and not method"
+    pytest -k 'MyClass and not method'
 
 This will run tests which contain names that match the given *string expression* (case-insensitive),
 which can include Python operators that use filenames, class names and function names as variables.
 The example above will run ``TestMyClass.test_something``  but not ``TestMyClass.test_method_simple``.
+Use ``""`` instead of ``''`` in expression when running this on Windows
 
 .. _nodeids:
 
-**Run tests by node ids**
+**Run tests by collection arguments**
 
-Each collected test is assigned a unique ``nodeid`` which consist of the module filename followed
-by specifiers like class names, function names and parameters from parametrization, separated by ``::`` characters.
+Pass the module filename relative to the working directory, followed by specifiers like the class name and function name
+separated by ``::`` characters, and parameters from parameterization enclosed in ``[]``.
 
 To run a specific test within a module:
 
 .. code-block:: bash
 
-    pytest test_mod.py::test_func
+    pytest tests/test_mod.py::test_func
 
+To run all tests in a class:
 
-Another example specifying a test method in the command line:
+.. code-block:: bash
+
+    pytest tests/test_mod.py::TestClass
+
+Specifying a specific test method:
+
+.. code-block:: bash
+
+    pytest tests/test_mod.py::TestClass::test_method
+
+Specifying a specific parametrization of a test:
 
 .. code-block:: bash
 
-    pytest test_mod.py::TestClass::test_method
+    pytest tests/test_mod.py::test_func[x1,y2]
 
 **Run tests by marker expressions**
 
+To run all tests which are decorated with the ``@pytest.mark.slow`` decorator:
+
 .. code-block:: bash
 
     pytest -m slow
 
-Will run all tests which are decorated with the ``@pytest.mark.slow`` decorator.
+
+To run all tests which are decorated with the annotated ``@pytest.mark.slow(phase=1)`` decorator,
+with the ``phase`` keyword argument set to ``1``:
+
+.. code-block:: bash
+
+    pytest -m "slow(phase=1)"
 
 For more information see :ref:`marks <mark>`.
 
@@ -79,6 +100,28 @@ For more information see :ref:`marks <mark>`.
 
 This will import ``pkg.testing`` and use its filesystem location to find and run tests from.
 
+.. _args-from-file:
+
+**Read arguments from file**
+
+.. versionadded:: 8.2
+
+All of the above can be read from a file using the ``@`` prefix:
+
+.. code-block:: bash
+
+    pytest @tests_to_run.txt
+
+where ``tests_to_run.txt`` contains an entry per line, e.g.:
+
+.. code-block:: text
+
+    tests/test_file.py
+    tests/test_mod.py::test_func[x1,y2]
+    tests/test_mod.py::TestClass
+    -m slow
+
+This file can also be generated using ``pytest --collect-only -q`` and modified as needed.
 
 Getting help on version, option names, environment variables
 --------------------------------------------------------------
@@ -119,7 +162,7 @@ You can early-load plugins (internal and external) explicitly in the command-lin
 The option receives a ``name`` parameter, which can be:
 
 * A full module dotted name, for example ``myproject.plugins``. This dotted name must be importable.
-* The entry-point name of a plugin. This is the name passed to ``setuptools`` when the plugin is
+* The entry-point name of a plugin. This is the name passed to ``importlib`` when the plugin is
   registered. For example to early-load the :pypi:`pytest-cov` plugin you can use::
 
     pytest -p pytest_cov
@@ -172,7 +215,8 @@ You can invoke ``pytest`` from Python code directly:
 
 this acts as if you would call "pytest" from the command line.
 It will not raise :class:`SystemExit` but return the :ref:`exit code <exit-codes>` instead.
-You can pass in options and arguments:
+If you don't pass it any arguments, ``main`` reads the arguments from the command line arguments of the process (:data:`sys.argv`), which may be undesirable.
+You can pass in options and arguments explicitly:
 
 .. code-block:: python
 
@@ -183,9 +227,10 @@ You can specify additional plugins to ``pytest.main``:
 .. code-block:: python
 
     # content of myinvoke.py
-    import pytest
     import sys
 
+    import pytest
+
 
     class MyPlugin:
         def pytest_sessionfinish(self):
diff --git a/doc/en/how-to/writing_hook_functions.rst b/doc/en/how-to/writing_hook_functions.rst
index 663bceb6321..f4c00d04fda 100644
--- a/doc/en/how-to/writing_hook_functions.rst
+++ b/doc/en/how-to/writing_hook_functions.rst
@@ -56,23 +56,17 @@ The remaining hook functions will not be called in this case.
 
 .. _`hookwrapper`:
 
-hookwrapper: executing around other hooks
+hook wrappers: executing around other hooks
 -------------------------------------------------
 
-.. currentmodule:: _pytest.core
-
-
-
 pytest plugins can implement hook wrappers which wrap the execution
 of other hook implementations.  A hook wrapper is a generator function
 which yields exactly once. When pytest invokes hooks it first executes
 hook wrappers and passes the same arguments as to the regular hooks.
 
 At the yield point of the hook wrapper pytest will execute the next hook
-implementations and return their result to the yield point in the form of
-a :py:class:`Result <pluggy._Result>` instance which encapsulates a result or
-exception info.  The yield point itself will thus typically not raise
-exceptions (unless there are bugs).
+implementations and return their result to the yield point, or will
+propagate an exception if they raised.
 
 Here is an example definition of a hook wrapper:
 
@@ -81,26 +75,35 @@ Here is an example definition of a hook wrapper:
     import pytest
 
 
-    @pytest.hookimpl(hookwrapper=True)
+    @pytest.hookimpl(wrapper=True)
     def pytest_pyfunc_call(pyfuncitem):
         do_something_before_next_hook_executes()
 
-        outcome = yield
-        # outcome.excinfo may be None or a (cls, val, tb) tuple
+        # If the outcome is an exception, will raise the exception.
+        res = yield
+
+        new_res = post_process_result(res)
 
-        res = outcome.get_result()  # will raise if outcome was exception
+        # Override the return value to the plugin system.
+        return new_res
 
-        post_process_result(res)
+The hook wrapper needs to return a result for the hook, or raise an exception.
 
-        outcome.force_result(new_res)  # to override the return value to the plugin system
+In many cases, the wrapper only needs to perform tracing or other side effects
+around the actual hook implementations, in which case it can return the result
+value of the ``yield``. The simplest (though useless) hook wrapper is
+``return (yield)``.
 
-Note that hook wrappers don't return results themselves, they merely
-perform tracing or other side effects around the actual hook implementations.
-If the result of the underlying hook is a mutable object, they may modify
-that result but it's probably better to avoid it.
+In other cases, the wrapper wants the adjust or adapt the result, in which case
+it can return a new value. If the result of the underlying hook is a mutable
+object, the wrapper may modify that result, but it's probably better to avoid it.
+
+If the hook implementation failed with an exception, the wrapper can handle that
+exception using a ``try-catch-finally`` around the ``yield``, by propagating it,
+suppressing it, or raising a different exception entirely.
 
 For more information, consult the
-:ref:`pluggy documentation about hookwrappers <pluggy:hookwrappers>`.
+:ref:`pluggy documentation about hook wrappers <pluggy:hookwrappers>`.
 
 .. _plugin-hookorder:
 
@@ -130,11 +133,14 @@ after others, i.e.  the position in the ``N``-sized list of functions:
 
 
     # Plugin 3
-    @pytest.hookimpl(hookwrapper=True)
+    @pytest.hookimpl(wrapper=True)
     def pytest_collection_modifyitems(items):
         # will execute even before the tryfirst one above!
-        outcome = yield
-        # will execute after all non-hookwrappers executed
+        try:
+            return (yield)
+        finally:
+            # will execute after all non-wrappers executed
+            ...
 
 Here is the order of execution:
 
@@ -149,13 +155,13 @@ Here is the order of execution:
    Plugin1).
 
 4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
-   point.  The yield receives a :py:class:`Result <pluggy._Result>` instance which encapsulates
-   the result from calling the non-wrappers.  Wrappers shall not modify the result.
+   point.  The yield receives the result from calling the non-wrappers, or raises
+   an exception if the non-wrappers raised.
 
-It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
-``hookwrapper=True`` in which case it will influence the ordering of hookwrappers
-among each other.
+It's possible to use ``tryfirst`` and ``trylast`` also on hook wrappers
+in which case it will influence the ordering of hook wrappers among each other.
 
+.. _`declaringhooks`:
 
 Declaring new hooks
 ------------------------
@@ -165,13 +171,11 @@ Declaring new hooks
     This is a quick overview on how to add new hooks and how they work in general, but a more complete
     overview can be found in `the pluggy documentation <https://pluggy.readthedocs.io/en/latest/>`__.
 
-.. currentmodule:: _pytest.hookspec
-
 Plugins and ``conftest.py`` files may declare new hooks that can then be
 implemented by other plugins in order to alter behaviour or interact with
 the new plugin:
 
-.. autofunction:: pytest_addhooks
+.. autofunction:: _pytest.hookspec.pytest_addhooks
     :noindex:
 
 Hooks are usually declared as do-nothing functions that contain only
@@ -194,7 +198,7 @@ class or module can then be passed to the ``pluginmanager`` using the ``pytest_a
 .. code-block:: python
 
     def pytest_addhooks(pluginmanager):
-        """ This example assumes the hooks are grouped in the 'sample_hook' module. """
+        """This example assumes the hooks are grouped in the 'sample_hook' module."""
         from my_app.tests import sample_hook
 
         pluginmanager.add_hookspecs(sample_hook)
@@ -249,18 +253,19 @@ and use pytest_addoption as follows:
 
    # contents of hooks.py
 
+
    # Use firstresult=True because we only want one plugin to define this
    # default value
    @hookspec(firstresult=True)
    def pytest_config_file_default_value():
-       """ Return the default value for the config file command line option. """
+       """Return the default value for the config file command line option."""
 
 
    # contents of myplugin.py
 
 
    def pytest_addhooks(pluginmanager):
-       """ This example assumes the hooks are grouped in the 'hooks' module. """
+       """This example assumes the hooks are grouped in the 'hooks' module."""
        from . import hooks
 
        pluginmanager.add_hookspecs(hooks)
@@ -321,7 +326,7 @@ Plugins often need to store data on :class:`~pytest.Item`\s in one hook
 implementation, and access it in another. One common solution is to just
 assign some private attribute directly on the item, but type-checkers like
 mypy frown upon this, and it may also cause conflicts with other plugins.
-So pytest offers a better way to do this, :attr:`_pytest.nodes.Node.stash <item.stash>`.
+So pytest offers a better way to do this, :attr:`item.stash <_pytest.nodes.Node.stash>`.
 
 To use the "stash" in your plugins, first create "stash keys" somewhere at the
 top level of your plugin:
diff --git a/doc/en/how-to/writing_plugins.rst b/doc/en/how-to/writing_plugins.rst
index 28ed8f5f76d..1bba9644649 100644
--- a/doc/en/how-to/writing_plugins.rst
+++ b/doc/en/how-to/writing_plugins.rst
@@ -16,8 +16,8 @@ reporting by calling :ref:`well specified hooks <hook-reference>` of the followi
 
 * builtin plugins: loaded from pytest's internal ``_pytest`` directory.
 
-* :ref:`external plugins <extplugins>`: modules discovered through
-  `setuptools entry points`_
+* :ref:`external plugins <extplugins>`: installed third-party modules discovered
+  through :ref:`entry points <pip-installable plugins>` in their packaging metadata
 
 * `conftest.py plugins`_: modules auto-discovered in test directories
 
@@ -42,28 +42,24 @@ Plugin discovery order at tool startup
 3. by scanning the command line for the ``-p name`` option
    and loading the specified plugin. This happens before normal command-line parsing.
 
-4. by loading all plugins registered through `setuptools entry points`_.
+4. by loading all plugins registered through installed third-party package
+   :ref:`entry points <pip-installable plugins>`, unless the
+   :envvar:`PYTEST_DISABLE_PLUGIN_AUTOLOAD` environment variable is set.
 
 5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable.
 
-6. by loading all :file:`conftest.py` files as inferred by the command line
-   invocation:
+6. by loading all "initial ":file:`conftest.py` files:
 
-   - if no test paths are specified, use the current dir as a test path
-   - if exists, load ``conftest.py`` and ``test*/conftest.py`` relative
-     to the directory part of the first test path. After the ``conftest.py``
-     file is loaded, load all plugins specified in its
-     :globalvar:`pytest_plugins` variable if present.
+   - determine the test paths: specified on the command line, otherwise in
+     :confval:`testpaths` if defined and running from the rootdir, otherwise the
+     current dir
+   - for each test path, load ``conftest.py`` and ``test*/conftest.py`` relative
+     to the directory part of the test path, if exist. Before a ``conftest.py``
+     file is loaded, load ``conftest.py`` files in all of its parent directories.
+     After a ``conftest.py`` file is loaded, recursively load all plugins specified
+     in its :globalvar:`pytest_plugins` variable if present.
 
-   Note that pytest does not find ``conftest.py`` files in deeper nested
-   sub directories at tool startup.  It is usually a good idea to keep
-   your ``conftest.py`` file in the top level test or project root directory.
 
-7. by recursively loading all plugins specified by the
-   :globalvar:`pytest_plugins` variable in ``conftest.py`` files.
-
-
-.. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/
 .. _`conftest.py plugins`:
 .. _`localplugin`:
 .. _`local conftest plugins`:
@@ -108,9 +104,9 @@ Here is how you might run it::
     See also: :ref:`pythonpath`.
 
 .. note::
-    Some hooks should be implemented only in plugins or conftest.py files situated at the
-    tests root directory due to how pytest discovers plugins during startup,
-    see the documentation of each hook for details.
+    Some hooks cannot be implemented in conftest.py files which are not
+    :ref:`initial <pluginorder>` due to how pytest discovers plugins during
+    startup. See the documentation of each hook for details.
 
 Writing your own plugin
 -----------------------
@@ -147,29 +143,33 @@ Making your plugin installable by others
 
 If you want to make your plugin externally available, you
 may define a so-called entry point for your distribution so
-that ``pytest`` finds your plugin module.  Entry points are
-a feature that is provided by :std:doc:`setuptools:index`. pytest looks up
-the ``pytest11`` entrypoint to discover its
-plugins and you can thus make your plugin available by defining
-it in your setuptools-invocation:
+that ``pytest`` finds your plugin module. Entry points are
+a feature that is provided by :std:doc:`packaging tools
+<packaging:specifications/entry-points>`.
 
-.. sourcecode:: python
+pytest looks up the ``pytest11`` entrypoint to discover its
+plugins, thus you can make your plugin available by defining
+it in your ``pyproject.toml`` file.
+
+.. sourcecode:: toml
+
+    # sample ./pyproject.toml file
+    [build-system]
+    requires = ["hatchling"]
+    build-backend = "hatchling.build"
 
-    # sample ./setup.py file
-    from setuptools import setup
+    [project]
+    name = "myproject"
+    classifiers = [
+        "Framework :: Pytest",
+    ]
 
-    setup(
-        name="myproject",
-        packages=["myproject"],
-        # the following makes a plugin available to pytest
-        entry_points={"pytest11": ["name_of_plugin = myproject.pluginmodule"]},
-        # custom PyPI classifier for pytest plugins
-        classifiers=["Framework :: Pytest"],
-    )
+    [project.entry-points.pytest11]
+    myproject = "myproject.pluginmodule"
 
 If a package is installed this way, ``pytest`` will load
 ``myproject.pluginmodule`` as a plugin which can define
-:ref:`hooks <hook-reference>`.
+:ref:`hooks <hook-reference>`. Confirm registration with ``pytest --trace-config``
 
 .. note::
 
@@ -268,8 +268,9 @@ of the variable will also be loaded as plugins, and so on.
     tests root directory is deprecated, and will raise a warning.
 
 This mechanism makes it easy to share fixtures within applications or even
-external applications without the need to create external plugins using
-the ``setuptools``'s entry point technique.
+external applications without the need to create external plugins using the
+:std:doc:`entry point packaging metadata
+<packaging:guides/creating-and-discovering-plugins>` technique.
 
 Plugins imported by :globalvar:`pytest_plugins` will also automatically be marked
 for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
@@ -367,7 +368,7 @@ string value of ``Hello World!`` if we do not supply a value or ``Hello
         def _hello(name=None):
             if not name:
                 name = request.config.getoption("name")
-            return "Hello {name}!".format(name=name)
+            return f"Hello {name}!"
 
         return _hello
 
@@ -445,9 +446,9 @@ in our ``pytest.ini`` to tell pytest where to look for example files.
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
-    rootdir: /home/sweet/project, configfile: pytest.ini
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
+    rootdir: /home/sweet/project
+    configfile: pytest.ini
     collected 2 items
 
     test_example.py ..                                                   [100%]
diff --git a/doc/en/how-to/xunit_setup.rst b/doc/en/how-to/xunit_setup.rst
index 5a97b2c85f1..3de6681ff8f 100644
--- a/doc/en/how-to/xunit_setup.rst
+++ b/doc/en/how-to/xunit_setup.rst
@@ -32,7 +32,7 @@ which will usually be called once for all the functions:
 .. code-block:: python
 
     def setup_module(module):
-        """ setup any state specific to the execution of the given module."""
+        """setup any state specific to the execution of the given module."""
 
 
     def teardown_module(module):
@@ -63,6 +63,8 @@ and after all test methods of the class are called:
         setup_class.
         """
 
+.. _xunit-method-setup:
+
 Method and function level setup/teardown
 -----------------------------------------------
 
diff --git a/doc/en/index.rst b/doc/en/index.rst
index 2f771aa3c42..6e04dbb9ed0 100644
--- a/doc/en/index.rst
+++ b/doc/en/index.rst
@@ -1,29 +1,54 @@
-:orphan:
-
-.. sidebar:: Next Open Trainings
+.. _features:
 
-   - `Professional Testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, February 1st to 3rd, 2022, Leipzig (Germany) and remote.
+.. sidebar:: **Next Open Trainings and Events**
 
-   Also see `previous talks and blogposts <talks.html>`_.
+    - `pytest - simple, rapid and fun testing with Python <https://pretalx.com/pyconde-pydata-2025/talk/PDBAXQ/>`_, at `PyConDE <https://2025.pycon.de/>`_, **April 24th** (1.5), Darmstadt, Germany
 
-.. _features:
+    Also see :doc:`previous talks and blogposts <talks>`
 
 pytest: helps you write better programs
 =======================================
 
+.. toctree::
+    :hidden:
+
+    getting-started
+    how-to/index
+    reference/index
+    explanation/index
+    example/index
+
+.. toctree::
+    :caption: About the project
+    :hidden:
+
+    changelog
+    contributing
+    backwards-compatibility
+    sponsor
+    tidelift
+    license
+    contact
+
+.. toctree::
+    :caption: Useful links
+    :hidden:
+
+    pytest @ PyPI <https://pypi.org/project/pytest/>
+    pytest @ GitHub <https://github.com/pytest-dev/pytest/>
+    Issue Tracker <https://github.com/pytest-dev/pytest/issues>
+    PDF Documentation <https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>
+
 .. module:: pytest
 
 The ``pytest`` framework makes it easy to write small, readable tests, and can
 scale to support complex functional testing for applications and libraries.
 
 
-**Pythons**: ``pytest`` requires: Python 3.6, 3.7, 3.8, 3.9, or PyPy3.
+``pytest`` requires: Python 3.8+ or PyPy3.
 
 **PyPI package name**: :pypi:`pytest`
 
-**Documentation as PDF**: `download latest <https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_
-
-
 A quick example
 ---------------
 
@@ -44,8 +69,7 @@ To execute it:
 
     $ pytest
     =========================== test session starts ============================
-    platform linux -- Python 3.x.y, pytest-6.x.y, py-1.x.y, pluggy-1.x.y
-    cachedir: .pytest_cache
+    platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
     rootdir: /home/sweet/project
     collected 1 item
 
@@ -77,17 +101,17 @@ Features
 
 - :ref:`Modular fixtures <fixture>` for managing small or parametrized long-lived test resources
 
-- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box
+- Can run :ref:`unittest <unittest>` (including trial) test suites out of the box
 
-- Python 3.6+ and PyPy 3
+- Python 3.8+ or PyPy 3
 
-- Rich plugin architecture, with over 800+ :ref:`external plugins <plugin-list>` and thriving community
+- Rich plugin architecture, with over 1300+ :ref:`external plugins <plugin-list>` and thriving community
 
 
 Documentation
 -------------
 
-* :ref:`Get started <get-started>` - install pytest and grasp its basics just twenty minutes
+* :ref:`Get started <get-started>` - install pytest and grasp its basics in just twenty minutes
 * :ref:`How-to guides <how-to>` - step-by-step guides, covering a vast range of use-cases and needs
 * :ref:`Reference guides <reference>` - includes the complete pytest API reference, lists of plugins and more
 * :ref:`Explanation <explanation>` - background, discussion of key topics, answers to higher-level questions
@@ -99,11 +123,6 @@ Bugs/Requests
 Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
 
 
-Changelog
----------
-
-Consult the :ref:`Changelog <changelog>` page for fixes and enhancements of each version.
-
 Support pytest
 --------------
 
@@ -136,13 +155,3 @@ Security
 pytest has never been associated with a security vulnerability, but in any case, to report a
 security vulnerability please use the `Tidelift security contact <https://tidelift.com/security>`_.
 Tidelift will coordinate the fix and disclosure.
-
-
-License
--------
-
-Copyright Holger Krekel and others, 2004.
-
-Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
-
-.. _`MIT`: https://github.com/pytest-dev/pytest/blob/main/LICENSE
diff --git a/doc/en/naming20.rst b/doc/en/naming20.rst
index 5a81df2698d..11213066384 100644
--- a/doc/en/naming20.rst
+++ b/doc/en/naming20.rst
@@ -8,7 +8,7 @@ If you used older version of the ``py`` distribution (which
 included the py.test command line tool and Python name space)
 you accessed helpers and possibly collection classes through
 the ``py.test`` Python namespaces.  The new ``pytest``
-Python module flaty provides the same objects, following
+Python module flatly provides the same objects, following
 these renaming rules::
 
     py.test.XYZ          -> pytest.XYZ
diff --git a/doc/en/py27-py34-deprecation.rst b/doc/en/py27-py34-deprecation.rst
deleted file mode 100644
index 660b078e30e..00000000000
--- a/doc/en/py27-py34-deprecation.rst
+++ /dev/null
@@ -1,99 +0,0 @@
-Python 2.7 and 3.4 support
-==========================
-
-It is demanding on the maintainers of an open source project to support many Python versions, as
-there's extra cost of keeping code compatible between all versions, while holding back on
-features only made possible on newer Python versions.
-
-In case of Python 2 and 3, the difference between the languages makes it even more prominent,
-because many new Python 3 features cannot be used in a Python 2/3 compatible code base.
-
-Python 2.7 EOL has been reached :pep:`in 2020 <0373#maintenance-releases>`, with
-the last release made in April, 2020.
-
-Python 3.4 EOL has been reached :pep:`in 2019 <0429#release-schedule>`, with the last release made in March, 2019.
-
-For those reasons, in Jun 2019 it was decided that **pytest 4.6** series will be the last to support Python 2.7 and 3.4.
-
-What this means for general users
----------------------------------
-
-Thanks to the `python_requires`_ setuptools option,
-Python 2.7 and Python 3.4 users using a modern pip version
-will install the last pytest 4.6.X version automatically even if 5.0 or later versions
-are available on PyPI.
-
-Users should ensure they are using the latest pip and setuptools versions for this to work.
-
-Maintenance of 4.6.X versions
------------------------------
-
-Until January 2020, the pytest core team ported many bug-fixes from the main release into the
-``4.6.x`` branch, with several 4.6.X releases being made along the year.
-
-From now on, the core team will **no longer actively backport patches**, but the ``4.6.x``
-branch will continue to exist so the community itself can contribute patches.
-
-The core team will be happy to accept those patches, and make new 4.6.X releases **until mid-2020**
-(but consider that date as a ballpark, after that date the team might still decide to make new releases
-for critical bugs).
-
-.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
-
-Technical aspects
-~~~~~~~~~~~~~~~~~
-
-(This section is a transcript from :issue:`5275`).
-
-In this section we describe the technical aspects of the Python 2.7 and 3.4 support plan.
-
-.. _what goes into 4.6.x releases:
-
-What goes into 4.6.X releases
-+++++++++++++++++++++++++++++
-
-New 4.6.X releases will contain bug fixes only.
-
-When will 4.6.X releases happen
-+++++++++++++++++++++++++++++++
-
-New 4.6.X releases will happen after we have a few bugs in place to release, or if a few weeks have
-passed (say a single bug has been fixed a month after the latest 4.6.X release).
-
-No hard rules here, just ballpark.
-
-Who will handle applying bug fixes
-++++++++++++++++++++++++++++++++++
-
-We core maintainers expect that people still using Python 2.7/3.4 and being affected by
-bugs to step up and provide patches and/or port bug fixes from the active branches.
-
-We will be happy to guide users interested in doing so, so please don't hesitate to ask.
-
-**Backporting changes into 4.6**
-
-Please follow these instructions:
-
-#. ``git fetch --all --prune``
-
-#. ``git checkout origin/4.6.x -b backport-XXXX`` # use the PR number here
-
-#. Locate the merge commit on the PR, in the *merged* message, for example:
-
-    nicoddemus merged commit 0f8b462 into pytest-dev:features
-
-#. ``git cherry-pick -m1 REVISION`` # use the revision you found above (``0f8b462``).
-
-#. Open a PR targeting ``4.6.x``:
-
-   * Prefix the message with ``[4.6]`` so it is an obvious backport
-   * Delete the PR body, it usually contains a duplicate commit message.
-
-**Providing new PRs to 4.6**
-
-Fresh pull requests to ``4.6.x`` will be accepted provided that
-the equivalent code in the active branches does not contain that bug (for example, a bug is specific
-to Python 2 only).
-
-Bug fixes that also happen in the mainstream version should be first fixed
-there, and then backported as per instructions above.
diff --git a/doc/en/reference/customize.rst b/doc/en/reference/customize.rst
index 22ce24b31e0..373223ec913 100644
--- a/doc/en/reference/customize.rst
+++ b/doc/en/reference/customize.rst
@@ -20,8 +20,7 @@ Configuration file formats
 --------------------------
 
 Many :ref:`pytest settings <ini options ref>` can be set in a *configuration file*, which
-by convention resides on the root of your repository or in your
-tests folder.
+by convention resides in the root directory of your repository.
 
 A quick example of the configuration files supported by pytest:
 
@@ -30,9 +29,11 @@ pytest.ini
 
 ``pytest.ini`` files take precedence over other files, even when empty.
 
+Alternatively, the hidden version ``.pytest.ini`` can be used.
+
 .. code-block:: ini
 
-    # pytest.ini
+    # pytest.ini or .pytest.ini
     [pytest]
     minversion = 6.0
     addopts = -ra -q
@@ -89,7 +90,7 @@ and can also be used to hold pytest configuration if they have a ``[pytest]`` se
 setup.cfg
 ~~~~~~~~~
 
-``setup.cfg`` files are general purpose configuration files, used originally by :doc:`distutils <distutils/configfile>`, and can also be used to hold pytest configuration
+``setup.cfg`` files are general purpose configuration files, used originally by ``distutils`` (now deprecated) and :std:doc:`setuptools <setuptools:userguide/declarative_config>`, and can also be used to hold pytest configuration
 if they have a ``[tool:pytest]`` section.
 
 .. code-block:: ini
@@ -176,13 +177,20 @@ Files will only be matched for configuration if:
 * ``tox.ini``: contains a ``[pytest]`` section.
 * ``setup.cfg``: contains a ``[tool:pytest]`` section.
 
+Finally, a ``pyproject.toml`` file will be considered the ``configfile`` if no other match was found, in this case
+even if it does not contain a ``[tool.pytest.ini_options]`` table (this was added in ``8.1``).
+
 The files are considered in the order above. Options from multiple ``configfiles`` candidates
 are never merged - the first match wins.
 
+The configuration file also determines the value of the ``rootpath``.
+
 The :class:`Config <pytest.Config>` object (accessible via hooks or through the :fixture:`pytestconfig` fixture)
 will subsequently carry these attributes:
 
-- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist.
+- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist. It is used as
+  a reference directory for constructing test addresses ("nodeids") and can be used also by plugins for storing
+  per-testrun information.
 
 - :attr:`config.inipath <pytest.Config.inipath>`: the determined ``configfile``, may be ``None``
   (it is named ``inipath`` for historical reasons).
@@ -192,9 +200,7 @@ will subsequently carry these attributes:
     versions of the older ``config.rootdir`` and ``config.inifile``, which have type
     ``py.path.local``, and still exist for backward compatibility.
 
-The ``rootdir`` is used as a reference directory for constructing test
-addresses ("nodeids") and can be used also by plugins for storing
-per-testrun information.
+
 
 Example:
 
diff --git a/doc/en/reference/fixtures.rst b/doc/en/reference/fixtures.rst
index 35b79021209..566304d3330 100644
--- a/doc/en/reference/fixtures.rst
+++ b/doc/en/reference/fixtures.rst
@@ -11,9 +11,6 @@ Fixtures reference
 .. seealso:: :ref:`about-fixtures`
 .. seealso:: :ref:`how-to-fixtures`
 
-
-.. currentmodule:: _pytest.python
-
 .. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection
 
 
@@ -35,6 +32,10 @@ Built-in fixtures
    :fixture:`capsys`
         Capture, as text, output to ``sys.stdout`` and ``sys.stderr``.
 
+   :fixture:`capteesys`
+        Capture in the same manner as :fixture:`capsys`, but also pass text
+        through according to ``--capture=``.
+
    :fixture:`capsysbinary`
         Capture, as bytes, output to ``sys.stdout`` and ``sys.stderr``.
 
@@ -42,7 +43,7 @@ Built-in fixtures
         Store and retrieve values across pytest runs.
 
    :fixture:`doctest_namespace`
-        Provide a dict injected into the docstests namespace.
+        Provide a dict injected into the doctests namespace.
 
    :fixture:`monkeypatch`
        Temporarily modify classes, functions, dictionaries,
@@ -76,15 +77,13 @@ Built-in fixtures
         :class:`pathlib.Path` objects.
 
    :fixture:`tmpdir`
-        Provide a :class:`py.path.local` object to a temporary
+        Provide a `py.path.local <https://py.readthedocs.io/en/latest/path.html>`_ object to a temporary
         directory which is unique to each test function;
         replaced by :fixture:`tmp_path`.
 
-        .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
-
    :fixture:`tmpdir_factory`
         Make session-scoped temporary directories and return
-        :class:`py.path.local` objects;
+        ``py.path.local`` objects;
         replaced by :fixture:`tmp_path_factory`.
 
 
@@ -98,7 +97,7 @@ Fixture availability is determined from the perspective of the test. A fixture
 is only available for tests to request if they are in the scope that fixture is
 defined in. If a fixture is defined inside a class, it can only be requested by
 tests inside that class. But if a fixture is defined inside the global scope of
-the module, than every test in that module, even if it's defined inside a class,
+the module, then every test in that module, even if it's defined inside a class,
 can request it.
 
 Similarly, a test can also only be affected by an autouse fixture if that test
@@ -208,7 +207,7 @@ the one defined in ``tests/test_top.py`` would be unavailable to it because it
 would have to step down a level (step inside a circle) to find it.
 
 The first fixture the test finds is the one that will be used, so
-:ref:`fixtures can be overriden <override fixtures>` if you need to change or
+:ref:`fixtures can be overridden <override fixtures>` if you need to change or
 extend what one does for a particular scope.
 
 You can also use the ``conftest.py`` file to implement
@@ -335,7 +334,7 @@ For example:
 
 .. literalinclude:: /example/fixtures/test_fixtures_order_dependencies.py
 
-If we map out what depends on what, we get something that look like this:
+If we map out what depends on what, we get something that looks like this:
 
 .. image:: /example/fixtures/test_fixtures_order_dependencies.*
     :align: center
@@ -401,6 +400,9 @@ the graph would look like this:
 Because ``c`` can now be put above ``d`` in the graph, pytest can once again
 linearize the graph to this:
 
+.. image:: /example/fixtures/test_fixtures_order_autouse_flat.*
+    :align: center
+
 In this example, ``c`` makes ``b`` and ``a`` effectively autouse fixtures as
 well.
 
diff --git a/doc/en/reference/index.rst b/doc/en/reference/index.rst
index d9648400317..ee1b2e6214d 100644
--- a/doc/en/reference/index.rst
+++ b/doc/en/reference/index.rst
@@ -8,8 +8,8 @@ Reference guides
 .. toctree::
    :maxdepth: 1
 
+   reference
    fixtures
-   plugin_list
    customize
-   reference
    exit-codes
+   plugin_list
diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst
index ebf40091369..406ebe75dce 100644
--- a/doc/en/reference/plugin_list.rst
+++ b/doc/en/reference/plugin_list.rst
@@ -1,1005 +1,1723 @@
 
+.. Note this file is autogenerated by scripts/update-plugin-list.py - usually weekly via github action
+
 .. _plugin-list:
 
-Plugin List
-===========
+Pytest Plugin List
+==================
+
+Below is an automated compilation of ``pytest``` plugins available on `PyPI <https://pypi.org>`_.
+It includes PyPI projects whose names begin with ``pytest-`` or ``pytest_`` and a handful of manually selected projects.
+Packages classified as inactive are excluded.
+
+For detailed insights into how this list is generated,
+please refer to `the update script <https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py>`_.
+
+.. warning::
+
+   Please be aware that this list is not a curated collection of projects
+   and does not undergo a systematic review process.
+   It serves purely as an informational resource to aid in the discovery of ``pytest`` plugins.
+
+   Do not presume any endorsement from the ``pytest`` project or its developers,
+   and always conduct your own quality assessment before incorporating any of these plugins into your own projects.
 
-PyPI projects that match "pytest-\*" are considered plugins and are listed
-automatically. Packages classified as inactive are excluded.
 
 .. The following conditional uses a different format for this list when
    creating a PDF, because otherwise the table gets far too wide for the
    page.
 
-This list contains 963 plugins.
+This list contains 1616 plugins.
 
 .. only:: not latex
 
-   ===============================================  ========================================================================================================================================================================  ==============  =====================  ================================================
-   name                                             summary                                                                                                                                                                   last release    status                 requires
-   ===============================================  ========================================================================================================================================================================  ==============  =====================  ================================================
-   :pypi:`pytest-accept`                            A pytest-plugin for updating doctest outputs                                                                                                                              Nov 22, 2021    N/A                    pytest (>=6,<7)
-   :pypi:`pytest-adaptavist`                        pytest plugin for generating test execution results within Jira Test Management (tm4j)                                                                                    Nov 30, 2021    N/A                    pytest (>=5.4.0)
-   :pypi:`pytest-addons-test`                       用于测试pytest的插件                                                                                                                                                      Aug 02, 2021    N/A                    pytest (>=6.2.4,<7.0.0)
-   :pypi:`pytest-adf`                               Pytest plugin for writing Azure Data Factory integration tests                                                                                                            May 10, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-adf-azure-identity`                Pytest plugin for writing Azure Data Factory integration tests                                                                                                            Mar 06, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-agent`                             Service that exposes a REST API that can be used to interract remotely with Pytest. It is shipped with a dashboard that enables running tests in a more convenient way.   Nov 25, 2021    N/A                    N/A
-   :pypi:`pytest-aggreport`                         pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details.                                                 Mar 07, 2021    4 - Beta               pytest (>=6.2.2)
-   :pypi:`pytest-aio`                               Pytest plugin for testing async python code                                                                                                                               Oct 20, 2021    4 - Beta               pytest
-   :pypi:`pytest-aiofiles`                          pytest fixtures for writing aiofiles tests with pyfakefs                                                                                                                  May 14, 2017    5 - Production/Stable  N/A
-   :pypi:`pytest-aiohttp`                           pytest plugin for aiohttp support                                                                                                                                         Dec 05, 2017    N/A                    pytest
-   :pypi:`pytest-aiohttp-client`                    Pytest \`client\` fixture for the Aiohttp                                                                                                                                 Nov 01, 2020    N/A                    pytest (>=6)
-   :pypi:`pytest-aioresponses`                      py.test integration for aioresponses                                                                                                                                      Jul 29, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-aioworkers`                        A plugin to test aioworkers project with pytest                                                                                                                           Dec 04, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-airflow`                           pytest support for airflow.                                                                                                                                               Apr 03, 2019    3 - Alpha              pytest (>=4.4.0)
-   :pypi:`pytest-airflow-utils`                                                                                                                                                                                               Nov 15, 2021    N/A                    N/A
-   :pypi:`pytest-alembic`                           A pytest plugin for verifying alembic migrations.                                                                                                                         Dec 02, 2021    N/A                    pytest (>=1.0)
-   :pypi:`pytest-allclose`                          Pytest fixture extending Numpy's allclose function                                                                                                                        Jul 30, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-allure-adaptor`                    Plugin for py.test to generate allure xml reports                                                                                                                         Jan 10, 2018    N/A                    pytest (>=2.7.3)
-   :pypi:`pytest-allure-adaptor2`                   Plugin for py.test to generate allure xml reports                                                                                                                         Oct 14, 2020    N/A                    pytest (>=2.7.3)
-   :pypi:`pytest-allure-dsl`                        pytest plugin to test case doc string dls instructions                                                                                                                    Oct 25, 2020    4 - Beta               pytest
-   :pypi:`pytest-allure-spec-coverage`              The pytest plugin aimed to display test coverage of the specs(requirements) in Allure                                                                                     Oct 26, 2021    N/A                    pytest
-   :pypi:`pytest-alphamoon`                         Static code checks used at Alphamoon                                                                                                                                      Oct 21, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-android`                           This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2.                                                                            Feb 21, 2019    3 - Alpha              pytest
-   :pypi:`pytest-anki`                              A pytest plugin for testing Anki add-ons                                                                                                                                  Oct 14, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-annotate`                          pytest-annotate: Generate PyAnnotate annotations from your pytest tests.                                                                                                  Nov 29, 2021    3 - Alpha              pytest (<7.0.0,>=3.2.0)
-   :pypi:`pytest-ansible`                           Plugin for py.test to simplify calling ansible modules from tests or fixtures                                                                                             May 25, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-ansible-playbook`                  Pytest fixture which runs given ansible playbook file.                                                                                                                    Mar 08, 2019    4 - Beta               N/A
-   :pypi:`pytest-ansible-playbook-runner`           Pytest fixture which runs given ansible playbook file.                                                                                                                    Dec 02, 2020    4 - Beta               pytest (>=3.1.0)
-   :pypi:`pytest-antilru`                           Bust functools.lru_cache when running pytest to avoid test pollution                                                                                                      Apr 11, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-anyio`                             The pytest anyio plugin is built into anyio. You don't need this package.                                                                                                 Jun 29, 2021    N/A                    pytest
-   :pypi:`pytest-anything`                          Pytest fixtures to assert anything and something                                                                                                                          Feb 18, 2021    N/A                    N/A
-   :pypi:`pytest-aoc`                               Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures                                                                                                Nov 23, 2021    N/A                    pytest ; extra == 'test'
-   :pypi:`pytest-api`                               PyTest-API Python Web Framework built for testing purposes.                                                                                                               May 04, 2021    N/A                    N/A
-   :pypi:`pytest-apistellar`                        apistellar plugin for pytest.                                                                                                                                             Jun 18, 2019    N/A                    N/A
-   :pypi:`pytest-appengine`                         AppEngine integration that works well with pytest-django                                                                                                                  Feb 27, 2017    N/A                    N/A
-   :pypi:`pytest-appium`                            Pytest plugin for appium                                                                                                                                                  Dec 05, 2019    N/A                    N/A
-   :pypi:`pytest-approvaltests`                     A plugin to use approvaltests with pytest                                                                                                                                 Feb 07, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-argus`                             pyest results colection plugin                                                                                                                                            Jun 24, 2021    5 - Production/Stable  pytest (>=6.2.4)
-   :pypi:`pytest-arraydiff`                         pytest plugin to help with comparing array output from tests                                                                                                              Dec 06, 2018    4 - Beta               pytest
-   :pypi:`pytest-asgi-server`                       Convenient ASGI client/server fixtures for Pytest                                                                                                                         Dec 12, 2020    N/A                    pytest (>=5.4.1)
-   :pypi:`pytest-asptest`                           test Answer Set Programming programs                                                                                                                                      Apr 28, 2018    4 - Beta               N/A
-   :pypi:`pytest-assertutil`                        pytest-assertutil                                                                                                                                                         May 10, 2019    N/A                    N/A
-   :pypi:`pytest-assert-utils`                      Useful assertion utilities for use with pytest                                                                                                                            Sep 21, 2021    3 - Alpha              N/A
-   :pypi:`pytest-assume`                            A pytest plugin that allows multiple failures per test                                                                                                                    Jun 24, 2021    N/A                    pytest (>=2.7)
-   :pypi:`pytest-ast-back-to-python`                A plugin for pytest devs to view how assertion rewriting recodes the AST                                                                                                  Sep 29, 2019    4 - Beta               N/A
-   :pypi:`pytest-astropy`                           Meta-package containing dependencies for testing                                                                                                                          Sep 21, 2021    5 - Production/Stable  pytest (>=4.6)
-   :pypi:`pytest-astropy-header`                    pytest plugin to add diagnostic information to the header of the test output                                                                                              Dec 18, 2019    3 - Alpha              pytest (>=2.8)
-   :pypi:`pytest-ast-transformer`                                                                                                                                                                                             May 04, 2019    3 - Alpha              pytest
-   :pypi:`pytest-asyncio`                           Pytest support for asyncio.                                                                                                                                               Oct 15, 2021    4 - Beta               pytest (>=5.4.0)
-   :pypi:`pytest-asyncio-cooperative`               Run all your asynchronous tests cooperatively.                                                                                                                            Oct 12, 2021    4 - Beta               N/A
-   :pypi:`pytest-asyncio-network-simulator`         pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests                                                                                    Jul 31, 2018    3 - Alpha              pytest (<3.7.0,>=3.3.2)
-   :pypi:`pytest-async-mongodb`                     pytest plugin for async MongoDB                                                                                                                                           Oct 18, 2017    5 - Production/Stable  pytest (>=2.5.2)
-   :pypi:`pytest-async-sqlalchemy`                  Database testing fixtures using the SQLAlchemy asyncio API                                                                                                                Oct 07, 2021    4 - Beta               pytest (>=6.0.0)
-   :pypi:`pytest-atomic`                            Skip rest of tests if previous test failed.                                                                                                                               Nov 24, 2018    4 - Beta               N/A
-   :pypi:`pytest-attrib`                            pytest plugin to select tests based on attributes similar to the nose-attrib plugin                                                                                       May 24, 2016    4 - Beta               N/A
-   :pypi:`pytest-austin`                            Austin plugin for pytest                                                                                                                                                  Oct 11, 2020    4 - Beta               N/A
-   :pypi:`pytest-autochecklog`                      automatically check condition and log all the checks                                                                                                                      Apr 25, 2015    4 - Beta               N/A
-   :pypi:`pytest-automation`                        pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality.                                                                    Oct 01, 2021    N/A                    pytest
-   :pypi:`pytest-automock`                          Pytest plugin for automatical mocks creation                                                                                                                              Apr 22, 2020    N/A                    pytest ; extra == 'dev'
-   :pypi:`pytest-auto-parametrize`                  pytest plugin: avoid repeating arguments in parametrize                                                                                                                   Oct 02, 2016    3 - Alpha              N/A
-   :pypi:`pytest-autotest`                          This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2.                                                                            Aug 25, 2021    N/A                    pytest
-   :pypi:`pytest-avoidance`                         Makes pytest skip tests that don not need rerunning                                                                                                                       May 23, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-aws`                               pytest plugin for testing AWS resource configurations                                                                                                                     Oct 04, 2017    4 - Beta               N/A
-   :pypi:`pytest-aws-config`                        Protect your AWS credentials in unit tests                                                                                                                                May 28, 2021    N/A                    N/A
-   :pypi:`pytest-axe`                               pytest plugin for axe-selenium-python                                                                                                                                     Nov 12, 2018    N/A                    pytest (>=3.0.0)
-   :pypi:`pytest-azurepipelines`                    Formatting PyTest output for Azure Pipelines UI                                                                                                                           Jul 23, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-bandit`                            A bandit plugin for pytest                                                                                                                                                Feb 23, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-base-url`                          pytest plugin for URL based testing                                                                                                                                       Jun 19, 2020    5 - Production/Stable  pytest (>=2.7.3)
-   :pypi:`pytest-bdd`                               BDD for pytest                                                                                                                                                            Oct 25, 2021    6 - Mature             pytest (>=4.3)
-   :pypi:`pytest-bdd-splinter`                      Common steps for pytest bdd and splinter integration                                                                                                                      Aug 12, 2019    5 - Production/Stable  pytest (>=4.0.0)
-   :pypi:`pytest-bdd-web`                           A simple plugin to use with pytest                                                                                                                                        Jan 02, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-bdd-wrappers`                                                                                                                                                                                                Feb 11, 2020    2 - Pre-Alpha          N/A
-   :pypi:`pytest-beakerlib`                         A pytest plugin that reports test results to the BeakerLib framework                                                                                                      Mar 17, 2017    5 - Production/Stable  pytest
-   :pypi:`pytest-beds`                              Fixtures for testing Google Appengine (GAE) apps                                                                                                                          Jun 07, 2016    4 - Beta               N/A
-   :pypi:`pytest-bench`                             Benchmark utility that plugs into pytest.                                                                                                                                 Jul 21, 2014    3 - Alpha              N/A
-   :pypi:`pytest-benchmark`                         A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer.                                              Apr 17, 2021    5 - Production/Stable  pytest (>=3.8)
-   :pypi:`pytest-bg-process`                        Pytest plugin to initialize background process                                                                                                                            Aug 17, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-bigchaindb`                        A BigchainDB plugin for pytest.                                                                                                                                           Aug 17, 2021    4 - Beta               N/A
-   :pypi:`pytest-bigquery-mock`                     Provides a mock fixture for python bigquery client                                                                                                                        Aug 05, 2021    N/A                    pytest (>=5.0)
-   :pypi:`pytest-black`                             A pytest plugin to enable format checking with black                                                                                                                      Oct 05, 2020    4 - Beta               N/A
-   :pypi:`pytest-black-multipy`                     Allow '--black' on older Pythons                                                                                                                                          Jan 14, 2021    5 - Production/Stable  pytest (!=3.7.3,>=3.5) ; extra == 'testing'
-   :pypi:`pytest-blame`                             A pytest plugin helps developers to debug by providing useful commits history.                                                                                            May 04, 2019    N/A                    pytest (>=4.4.0)
-   :pypi:`pytest-blender`                           Blender Pytest plugin.                                                                                                                                                    Oct 29, 2021    N/A                    pytest (==6.2.5) ; extra == 'dev'
-   :pypi:`pytest-blink1`                            Pytest plugin to emit notifications via the Blink(1) RGB LED                                                                                                              Jan 07, 2018    4 - Beta               N/A
-   :pypi:`pytest-blockage`                          Disable network requests during a test run.                                                                                                                               Feb 13, 2019    N/A                    pytest
-   :pypi:`pytest-blocker`                           pytest plugin to mark a test as blocker and skip all other tests                                                                                                          Sep 07, 2015    4 - Beta               N/A
-   :pypi:`pytest-board`                             Local continuous test runner with pytest and watchdog.                                                                                                                    Jan 20, 2019    N/A                    N/A
-   :pypi:`pytest-bpdb`                              A py.test plug-in to enable drop to bpdb debugger on test failure.                                                                                                        Jan 19, 2015    2 - Pre-Alpha          N/A
-   :pypi:`pytest-bravado`                           Pytest-bravado automatically generates from OpenAPI specification client fixtures.                                                                                        Jul 19, 2021    N/A                    N/A
-   :pypi:`pytest-breakword`                         Use breakword with pytest                                                                                                                                                 Aug 04, 2021    N/A                    pytest (>=6.2.4,<7.0.0)
-   :pypi:`pytest-breed-adapter`                     A simple plugin to connect with breed-server                                                                                                                              Nov 07, 2018    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-briefcase`                         A pytest plugin for running tests on a Briefcase project.                                                                                                                 Jun 14, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-browser`                           A pytest plugin for console based browser test selection just after the collection phase                                                                                  Dec 10, 2016    3 - Alpha              N/A
-   :pypi:`pytest-browsermob-proxy`                  BrowserMob proxy plugin for py.test.                                                                                                                                      Jun 11, 2013    4 - Beta               N/A
-   :pypi:`pytest-browserstack-local`                \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background.                                                                                                    Feb 09, 2018    N/A                    N/A
-   :pypi:`pytest-bug`                               Pytest plugin for marking tests as a bug                                                                                                                                  Jun 02, 2020    5 - Production/Stable  pytest (>=3.6.0)
-   :pypi:`pytest-bugtong-tag`                       pytest-bugtong-tag is a plugin for pytest                                                                                                                                 Apr 23, 2021    N/A                    N/A
-   :pypi:`pytest-bugzilla`                          py.test bugzilla integration plugin                                                                                                                                       May 05, 2010    4 - Beta               N/A
-   :pypi:`pytest-bugzilla-notifier`                 A plugin that allows you to execute create, update, and read information from BugZilla bugs                                                                               Jun 15, 2018    4 - Beta               pytest (>=2.9.2)
-   :pypi:`pytest-buildkite`                         Plugin for pytest that automatically publishes coverage and pytest report annotations to Buildkite.                                                                       Jul 13, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-builtin-types`                                                                                                                                                                                               Nov 17, 2021    N/A                    pytest
-   :pypi:`pytest-bwrap`                             Run your tests in Bubblewrap sandboxes                                                                                                                                    Oct 26, 2018    3 - Alpha              N/A
-   :pypi:`pytest-cache`                             pytest plugin with mechanisms for caching across test runs                                                                                                                Jun 04, 2013    3 - Alpha              N/A
-   :pypi:`pytest-cache-assert`                      Cache assertion data to simplify regression testing of complex serializable data                                                                                          Nov 03, 2021    4 - Beta               pytest (>=5)
-   :pypi:`pytest-cagoule`                           Pytest plugin to only run tests affected by changes                                                                                                                       Jan 01, 2020    3 - Alpha              N/A
-   :pypi:`pytest-camel-collect`                     Enable CamelCase-aware pytest class collection                                                                                                                            Aug 02, 2020    N/A                    pytest (>=2.9)
-   :pypi:`pytest-canonical-data`                    A plugin which allows to compare results with canonical results, based on previous runs                                                                                   May 08, 2020    2 - Pre-Alpha          pytest (>=3.5.0)
-   :pypi:`pytest-caprng`                            A plugin that replays pRNG state on failure.                                                                                                                              May 02, 2018    4 - Beta               N/A
-   :pypi:`pytest-capture-deprecatedwarnings`        pytest plugin to capture all deprecatedwarnings and put them in one file                                                                                                  Apr 30, 2019    N/A                    N/A
-   :pypi:`pytest-capturelogs`                       A sample Python project                                                                                                                                                   Sep 11, 2021    3 - Alpha              N/A
-   :pypi:`pytest-cases`                             Separate test code from test cases in pytest.                                                                                                                             Nov 08, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-cassandra`                         Cassandra CCM Test Fixtures for pytest                                                                                                                                    Nov 04, 2017    1 - Planning           N/A
-   :pypi:`pytest-catchlog`                          py.test plugin to catch log messages. This is a fork of pytest-capturelog.                                                                                                Jan 24, 2016    4 - Beta               pytest (>=2.6)
-   :pypi:`pytest-catch-server`                      Pytest plugin with server for catching HTTP requests.                                                                                                                     Dec 12, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-celery`                            pytest-celery a shim pytest plugin to enable celery.contrib.pytest                                                                                                        May 06, 2021    N/A                    N/A
-   :pypi:`pytest-chainmaker`                        pytest plugin for chainmaker                                                                                                                                              Oct 15, 2021    N/A                    N/A
-   :pypi:`pytest-chalice`                           A set of py.test fixtures for AWS Chalice                                                                                                                                 Jul 01, 2020    4 - Beta               N/A
-   :pypi:`pytest-change-report`                     turn . into √,turn F into x                                                                                                                                              Sep 14, 2020    N/A                    pytest
-   :pypi:`pytest-chdir`                             A pytest fixture for changing current working directory                                                                                                                   Jan 28, 2020    N/A                    pytest (>=5.0.0,<6.0.0)
-   :pypi:`pytest-checkdocs`                         check the README when running tests                                                                                                                                       Jul 31, 2021    5 - Production/Stable  pytest (>=4.6) ; extra == 'testing'
-   :pypi:`pytest-checkipdb`                         plugin to check if there are ipdb debugs left                                                                                                                             Jul 22, 2020    5 - Production/Stable  pytest (>=2.9.2)
-   :pypi:`pytest-check-links`                       Check links in files                                                                                                                                                      Jul 29, 2020    N/A                    pytest (>=4.6)
-   :pypi:`pytest-check-mk`                          pytest plugin to test Check_MK checks                                                                                                                                     Nov 19, 2015    4 - Beta               pytest
-   :pypi:`pytest-circleci`                          py.test plugin for CircleCI                                                                                                                                               May 03, 2019    N/A                    N/A
-   :pypi:`pytest-circleci-parallelized`             Parallelize pytest across CircleCI workers.                                                                                                                               Mar 26, 2019    N/A                    N/A
-   :pypi:`pytest-ckan`                              Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8                                                                                                               Apr 28, 2020    4 - Beta               pytest
-   :pypi:`pytest-clarity`                           A plugin providing an alternative, colourful diff output for failing assertions.                                                                                          Jun 11, 2021    N/A                    N/A
-   :pypi:`pytest-cldf`                              Easy quality control for CLDF datasets using pytest                                                                                                                       May 06, 2019    N/A                    N/A
-   :pypi:`pytest-click`                             Py.test plugin for Click                                                                                                                                                  Aug 29, 2020    5 - Production/Stable  pytest (>=5.0)
-   :pypi:`pytest-clld`                                                                                                                                                                                                        Nov 29, 2021    N/A                    pytest (>=3.6)
-   :pypi:`pytest-cloud`                             Distributed tests planner plugin for pytest testing framework.                                                                                                            Oct 05, 2020    6 - Mature             N/A
-   :pypi:`pytest-cloudflare-worker`                 pytest plugin for testing cloudflare workers                                                                                                                              Mar 30, 2021    4 - Beta               pytest (>=6.0.0)
-   :pypi:`pytest-cobra`                             PyTest plugin for testing Smart Contracts for Ethereum blockchain.                                                                                                        Jun 29, 2019    3 - Alpha              pytest (<4.0.0,>=3.7.1)
-   :pypi:`pytest-codeblocks`                        Test code blocks in your READMEs                                                                                                                                          Oct 13, 2021    4 - Beta               pytest (>=6)
-   :pypi:`pytest-codecheckers`                      pytest plugin to add source code sanity checks (pep8 and friends)                                                                                                         Feb 13, 2010    N/A                    N/A
-   :pypi:`pytest-codecov`                           Pytest plugin for uploading pytest-cov results to codecov.io                                                                                                              Oct 27, 2021    4 - Beta               pytest (>=4.6.0)
-   :pypi:`pytest-codegen`                           Automatically create pytest test signatures                                                                                                                               Aug 23, 2020    2 - Pre-Alpha          N/A
-   :pypi:`pytest-codestyle`                         pytest plugin to run pycodestyle                                                                                                                                          Mar 23, 2020    3 - Alpha              N/A
-   :pypi:`pytest-collect-formatter`                 Formatter for pytest collect output                                                                                                                                       Mar 29, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-collect-formatter2`                Formatter for pytest collect output                                                                                                                                       May 31, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-colordots`                         Colorizes the progress indicators                                                                                                                                         Oct 06, 2017    5 - Production/Stable  N/A
-   :pypi:`pytest-commander`                         An interactive GUI test runner for PyTest                                                                                                                                 Aug 17, 2021    N/A                    pytest (<7.0.0,>=6.2.4)
-   :pypi:`pytest-common-subject`                    pytest framework for testing different aspects of a common method                                                                                                         Nov 12, 2020    N/A                    pytest (>=3.6,<7)
-   :pypi:`pytest-concurrent`                        Concurrently execute test cases with multithread, multiprocess and gevent                                                                                                 Jan 12, 2019    4 - Beta               pytest (>=3.1.1)
-   :pypi:`pytest-config`                            Base configurations and utilities for developing    your Python project test suite with pytest.                                                                           Nov 07, 2014    5 - Production/Stable  N/A
-   :pypi:`pytest-confluence-report`                 Package stands for pytest plugin to upload results into Confluence page.                                                                                                  Nov 06, 2020    N/A                    N/A
-   :pypi:`pytest-console-scripts`                   Pytest plugin for testing console scripts                                                                                                                                 Sep 28, 2021    4 - Beta               N/A
-   :pypi:`pytest-consul`                            pytest plugin with fixtures for testing consul aware apps                                                                                                                 Nov 24, 2018    3 - Alpha              pytest
-   :pypi:`pytest-container`                         Pytest fixtures for writing container based tests                                                                                                                         Nov 19, 2021    3 - Alpha              pytest (>=3.10)
-   :pypi:`pytest-contextfixture`                    Define pytest fixtures as context managers.                                                                                                                               Mar 12, 2013    4 - Beta               N/A
-   :pypi:`pytest-contexts`                          A plugin to run tests written with the Contexts framework using pytest                                                                                                    May 19, 2021    4 - Beta               N/A
-   :pypi:`pytest-cookies`                           The pytest plugin for your Cookiecutter templates. 🍪                                                                                                                     May 24, 2021    5 - Production/Stable  pytest (>=3.3.0)
-   :pypi:`pytest-couchdbkit`                        py.test extension for per-test couchdb databases using couchdbkit                                                                                                         Apr 17, 2012    N/A                    N/A
-   :pypi:`pytest-count`                             count erros and send email                                                                                                                                                Jan 12, 2018    4 - Beta               N/A
-   :pypi:`pytest-cov`                               Pytest plugin for measuring coverage.                                                                                                                                     Oct 04, 2021    5 - Production/Stable  pytest (>=4.6)
-   :pypi:`pytest-cover`                             Pytest plugin for measuring coverage. Forked from \`pytest-cov\`.                                                                                                         Aug 01, 2015    5 - Production/Stable  N/A
-   :pypi:`pytest-coverage`                                                                                                                                                                                                    Jun 17, 2015    N/A                    N/A
-   :pypi:`pytest-coverage-context`                  Coverage dynamic context support for PyTest, including sub-processes                                                                                                      Jan 04, 2021    4 - Beta               pytest (>=6.1.0)
-   :pypi:`pytest-cov-exclude`                       Pytest plugin for excluding tests based on coverage data                                                                                                                  Apr 29, 2016    4 - Beta               pytest (>=2.8.0,<2.9.0); extra == 'dev'
-   :pypi:`pytest-cpp`                               Use pytest's runner to discover and execute C++ tests                                                                                                                     Dec 03, 2021    5 - Production/Stable  pytest (!=5.4.0,!=5.4.1)
-   :pypi:`pytest-cram`                              Run cram tests with pytest.                                                                                                                                               Aug 08, 2020    N/A                    N/A
-   :pypi:`pytest-crate`                             Manages CrateDB instances during your integration tests                                                                                                                   May 28, 2019    3 - Alpha              pytest (>=4.0)
-   :pypi:`pytest-cricri`                            A Cricri plugin for pytest.                                                                                                                                               Jan 27, 2018    N/A                    pytest
-   :pypi:`pytest-crontab`                           add crontab task in crontab                                                                                                                                               Dec 09, 2019    N/A                    N/A
-   :pypi:`pytest-csv`                               CSV output for pytest.                                                                                                                                                    Apr 22, 2021    N/A                    pytest (>=6.0)
-   :pypi:`pytest-curio`                             Pytest support for curio.                                                                                                                                                 Oct 07, 2020    N/A                    N/A
-   :pypi:`pytest-curl-report`                       pytest plugin to generate curl command line report                                                                                                                        Dec 11, 2016    4 - Beta               N/A
-   :pypi:`pytest-custom-concurrency`                Custom grouping concurrence for pytest                                                                                                                                    Feb 08, 2021    N/A                    N/A
-   :pypi:`pytest-custom-exit-code`                  Exit pytest test session with custom exit code in different scenarios                                                                                                     Aug 07, 2019    4 - Beta               pytest (>=4.0.2)
-   :pypi:`pytest-custom-nodeid`                     Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report                                                                     Mar 07, 2021    N/A                    N/A
-   :pypi:`pytest-custom-report`                     Configure the symbols displayed for test outcomes                                                                                                                         Jan 30, 2019    N/A                    pytest
-   :pypi:`pytest-custom-scheduling`                 Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report                                                                     Mar 01, 2021    N/A                    N/A
-   :pypi:`pytest-cython`                            A plugin for testing Cython extension modules                                                                                                                             Jan 26, 2021    4 - Beta               pytest (>=2.7.3)
-   :pypi:`pytest-darker`                            A pytest plugin for checking of modified code using Darker                                                                                                                Aug 16, 2020    N/A                    pytest (>=6.0.1) ; extra == 'test'
-   :pypi:`pytest-dash`                              pytest fixtures to run dash applications.                                                                                                                                 Mar 18, 2019    N/A                    N/A
-   :pypi:`pytest-data`                              Useful functions for managing data for pytest fixtures                                                                                                                    Nov 01, 2016    5 - Production/Stable  N/A
-   :pypi:`pytest-databricks`                        Pytest plugin for remote Databricks notebooks testing                                                                                                                     Jul 29, 2020    N/A                    pytest
-   :pypi:`pytest-datadir`                           pytest plugin for test data directories and files                                                                                                                         Oct 22, 2019    5 - Production/Stable  pytest (>=2.7.0)
-   :pypi:`pytest-datadir-mgr`                       Manager for test data providing downloads, caching of generated files, and a context for temp directories.                                                                Aug 16, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-datadir-ng`                        Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem.                                                          Dec 25, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-data-file`                         Fixture "data" and "case_data" for test from yaml file                                                                                                                    Dec 04, 2019    N/A                    N/A
-   :pypi:`pytest-datafiles`                         py.test plugin to create a 'tmpdir' containing predefined files/directories.                                                                                              Oct 07, 2018    5 - Production/Stable  pytest (>=3.6)
-   :pypi:`pytest-datafixtures`                      Data fixtures for pytest made simple                                                                                                                                      Dec 05, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-data-from-files`                   pytest plugin to provide data from files loaded automatically                                                                                                             Oct 13, 2021    4 - Beta               pytest
-   :pypi:`pytest-dataplugin`                        A pytest plugin for managing an archive of test data.                                                                                                                     Sep 16, 2017    1 - Planning           N/A
-   :pypi:`pytest-datarecorder`                      A py.test plugin recording and comparing test output.                                                                                                                     Apr 20, 2020    5 - Production/Stable  pytest
-   :pypi:`pytest-datatest`                          A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration).                                                        Oct 15, 2020    4 - Beta               pytest (>=3.3)
-   :pypi:`pytest-db`                                Session scope fixture "db" for mysql query or change                                                                                                                      Dec 04, 2019    N/A                    N/A
-   :pypi:`pytest-dbfixtures`                        Databases fixtures plugin for py.test.                                                                                                                                    Dec 07, 2016    4 - Beta               N/A
-   :pypi:`pytest-db-plugin`                                                                                                                                                                                                   Nov 27, 2021    N/A                    pytest (>=5.0)
-   :pypi:`pytest-dbt-adapter`                       A pytest plugin for testing dbt adapter plugins                                                                                                                           Nov 24, 2021    N/A                    pytest (<7,>=6)
-   :pypi:`pytest-dbus-notification`                 D-BUS notifications for pytest results.                                                                                                                                   Mar 05, 2014    5 - Production/Stable  N/A
-   :pypi:`pytest-deadfixtures`                      A simple plugin to list unused fixtures in pytest                                                                                                                         Jul 23, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-deepcov`                           deepcov                                                                                                                                                                   Mar 30, 2021    N/A                    N/A
-   :pypi:`pytest-defer`                                                                                                                                                                                                       Aug 24, 2021    N/A                    N/A
-   :pypi:`pytest-demo-plugin`                       pytest示例插件                                                                                                                                                            May 15, 2021    N/A                    N/A
-   :pypi:`pytest-dependency`                        Manage dependencies of tests                                                                                                                                              Feb 14, 2020    4 - Beta               N/A
-   :pypi:`pytest-depends`                           Tests that depend on other tests                                                                                                                                          Apr 05, 2020    5 - Production/Stable  pytest (>=3)
-   :pypi:`pytest-deprecate`                         Mark tests as testing a deprecated feature with a warning note.                                                                                                           Jul 01, 2019    N/A                    N/A
-   :pypi:`pytest-describe`                          Describe-style plugin for pytest                                                                                                                                          Nov 13, 2021    4 - Beta               pytest (>=4.0.0)
-   :pypi:`pytest-describe-it`                       plugin for rich text descriptions                                                                                                                                         Jul 19, 2019    4 - Beta               pytest
-   :pypi:`pytest-devpi-server`                      DevPI server fixture for py.test                                                                                                                                          May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-diamond`                           pytest plugin for diamond                                                                                                                                                 Aug 31, 2015    4 - Beta               N/A
-   :pypi:`pytest-dicom`                             pytest plugin to provide DICOM fixtures                                                                                                                                   Dec 19, 2018    3 - Alpha              pytest
-   :pypi:`pytest-dictsdiff`                                                                                                                                                                                                   Jul 26, 2019    N/A                    N/A
-   :pypi:`pytest-diff`                              A simple plugin to use with pytest                                                                                                                                        Mar 30, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-disable`                           pytest plugin to disable a test and skip it from testrun                                                                                                                  Sep 10, 2015    4 - Beta               N/A
-   :pypi:`pytest-disable-plugin`                    Disable plugins per test                                                                                                                                                  Feb 28, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-discord`                           A pytest plugin to notify test results to a Discord channel.                                                                                                              Mar 20, 2021    3 - Alpha              pytest (!=6.0.0,<7,>=3.3.2)
-   :pypi:`pytest-django`                            A Django plugin for pytest.                                                                                                                                               Dec 02, 2021    5 - Production/Stable  pytest (>=5.4.0)
-   :pypi:`pytest-django-ahead`                      A Django plugin for pytest.                                                                                                                                               Oct 27, 2016    5 - Production/Stable  pytest (>=2.9)
-   :pypi:`pytest-djangoapp`                         Nice pytest plugin to help you with Django pluggable application testing.                                                                                                 Aug 04, 2021    4 - Beta               N/A
-   :pypi:`pytest-django-cache-xdist`                A djangocachexdist plugin for pytest                                                                                                                                      May 12, 2020    4 - Beta               N/A
-   :pypi:`pytest-django-casperjs`                   Integrate CasperJS with your django tests as a pytest fixture.                                                                                                            Mar 15, 2015    2 - Pre-Alpha          N/A
-   :pypi:`pytest-django-dotenv`                     Pytest plugin used to setup environment variables with django-dotenv                                                                                                      Nov 26, 2019    4 - Beta               pytest (>=2.6.0)
-   :pypi:`pytest-django-factories`                  Factories for your Django models that can be used as Pytest fixtures.                                                                                                     Nov 12, 2020    4 - Beta               N/A
-   :pypi:`pytest-django-gcir`                       A Django plugin for pytest.                                                                                                                                               Mar 06, 2018    5 - Production/Stable  N/A
-   :pypi:`pytest-django-haystack`                   Cleanup your Haystack indexes between tests                                                                                                                               Sep 03, 2017    5 - Production/Stable  pytest (>=2.3.4)
-   :pypi:`pytest-django-ifactory`                   A model instance factory for pytest-django                                                                                                                                Jan 13, 2021    3 - Alpha              N/A
-   :pypi:`pytest-django-lite`                       The bare minimum to integrate py.test with Django.                                                                                                                        Jan 30, 2014    N/A                    N/A
-   :pypi:`pytest-django-liveserver-ssl`                                                                                                                                                                                       Jul 30, 2021    3 - Alpha              N/A
-   :pypi:`pytest-django-model`                      A Simple Way to Test your Django Models                                                                                                                                   Feb 14, 2019    4 - Beta               N/A
-   :pypi:`pytest-django-ordering`                   A pytest plugin for preserving the order in which Django runs tests.                                                                                                      Jul 25, 2019    5 - Production/Stable  pytest (>=2.3.0)
-   :pypi:`pytest-django-queries`                    Generate performance reports from your django database performance tests.                                                                                                 Mar 01, 2021    N/A                    N/A
-   :pypi:`pytest-djangorestframework`               A djangorestframework plugin for pytest                                                                                                                                   Aug 11, 2019    4 - Beta               N/A
-   :pypi:`pytest-django-rq`                         A pytest plugin to help writing unit test for django-rq                                                                                                                   Apr 13, 2020    4 - Beta               N/A
-   :pypi:`pytest-django-sqlcounts`                  py.test plugin for reporting the number of SQLs executed per django testcase.                                                                                             Jun 16, 2015    4 - Beta               N/A
-   :pypi:`pytest-django-testing-postgresql`         Use a temporary PostgreSQL database with pytest-django                                                                                                                    Dec 05, 2019    3 - Alpha              N/A
-   :pypi:`pytest-doc`                               A documentation plugin for py.test.                                                                                                                                       Jun 28, 2015    5 - Production/Stable  N/A
-   :pypi:`pytest-docgen`                            An RST Documentation Generator for pytest-based test suites                                                                                                               Apr 17, 2020    N/A                    N/A
-   :pypi:`pytest-docker`                            Simple pytest fixtures for Docker and docker-compose based tests                                                                                                          Jun 14, 2021    N/A                    pytest (<7.0,>=4.0)
-   :pypi:`pytest-docker-butla`                                                                                                                                                                                                Jun 16, 2019    3 - Alpha              N/A
-   :pypi:`pytest-dockerc`                           Run, manage and stop Docker Compose project from Docker API                                                                                                               Oct 09, 2020    5 - Production/Stable  pytest (>=3.0)
-   :pypi:`pytest-docker-compose`                    Manages Docker containers during your integration tests                                                                                                                   Jan 26, 2021    5 - Production/Stable  pytest (>=3.3)
-   :pypi:`pytest-docker-db`                         A plugin to use docker databases for pytests                                                                                                                              Mar 20, 2021    5 - Production/Stable  pytest (>=3.1.1)
-   :pypi:`pytest-docker-fixtures`                   pytest docker fixtures                                                                                                                                                    Nov 23, 2021    3 - Alpha              N/A
-   :pypi:`pytest-docker-git-fixtures`               Pytest fixtures for testing with git scm.                                                                                                                                 Mar 11, 2021    4 - Beta               pytest
-   :pypi:`pytest-docker-pexpect`                    pytest plugin for writing functional tests with pexpect and docker                                                                                                        Jan 14, 2019    N/A                    pytest
-   :pypi:`pytest-docker-postgresql`                 A simple plugin to use with pytest                                                                                                                                        Sep 24, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-docker-py`                         Easy to use, simple to extend, pytest plugin that minimally leverages docker-py.                                                                                          Nov 27, 2018    N/A                    pytest (==4.0.0)
-   :pypi:`pytest-docker-registry-fixtures`          Pytest fixtures for testing with docker registries.                                                                                                                       Mar 04, 2021    4 - Beta               pytest
-   :pypi:`pytest-docker-tools`                      Docker integration tests for pytest                                                                                                                                       Jul 23, 2021    4 - Beta               pytest (>=6.0.1,<7.0.0)
-   :pypi:`pytest-docs`                              Documentation tool for pytest                                                                                                                                             Nov 11, 2018    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-docstyle`                          pytest plugin to run pydocstyle                                                                                                                                           Mar 23, 2020    3 - Alpha              N/A
-   :pypi:`pytest-doctest-custom`                    A py.test plugin for customizing string representations of doctest results.                                                                                               Jul 25, 2016    4 - Beta               N/A
-   :pypi:`pytest-doctest-ellipsis-markers`          Setup additional values for ELLIPSIS_MARKER for doctests                                                                                                                  Jan 12, 2018    4 - Beta               N/A
-   :pypi:`pytest-doctest-import`                    A simple pytest plugin to import names and add them to the doctest namespace.                                                                                             Nov 13, 2018    4 - Beta               pytest (>=3.3.0)
-   :pypi:`pytest-doctestplus`                       Pytest plugin with advanced doctest features.                                                                                                                             Nov 16, 2021    3 - Alpha              pytest (>=4.6)
-   :pypi:`pytest-doctest-ufunc`                     A plugin to run doctests in docstrings of Numpy ufuncs                                                                                                                    Aug 02, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-dolphin`                           Some extra stuff that we use ininternally                                                                                                                                 Nov 30, 2016    4 - Beta               pytest (==3.0.4)
-   :pypi:`pytest-doorstop`                          A pytest plugin for adding test results into doorstop items.                                                                                                              Jun 09, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-dotenv`                            A py.test plugin that parses environment files before running tests                                                                                                       Jun 16, 2020    4 - Beta               pytest (>=5.0.0)
-   :pypi:`pytest-drf`                               A Django REST framework plugin for pytest.                                                                                                                                Nov 12, 2020    5 - Production/Stable  pytest (>=3.6)
-   :pypi:`pytest-drivings`                          Tool to allow webdriver automation to be ran locally or remotely                                                                                                          Jan 13, 2021    N/A                    N/A
-   :pypi:`pytest-drop-dup-tests`                    A Pytest plugin to drop duplicated tests during collection                                                                                                                May 23, 2020    4 - Beta               pytest (>=2.7)
-   :pypi:`pytest-dummynet`                          A py.test plugin providing access to a dummynet.                                                                                                                          Oct 13, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-dump2json`                         A pytest plugin for dumping test results to json.                                                                                                                         Jun 29, 2015    N/A                    N/A
-   :pypi:`pytest-duration-insights`                                                                                                                                                                                           Jun 25, 2021    N/A                    N/A
-   :pypi:`pytest-dynamicrerun`                      A pytest plugin to rerun tests dynamically based off of test outcome and output.                                                                                          Aug 15, 2020    4 - Beta               N/A
-   :pypi:`pytest-dynamodb`                          DynamoDB fixtures for pytest                                                                                                                                              Jun 03, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-easy-addoption`                    pytest-easy-addoption: Easy way to work with pytest addoption                                                                                                             Jan 22, 2020    N/A                    N/A
-   :pypi:`pytest-easy-api`                          Simple API testing with pytest                                                                                                                                            Mar 26, 2018    N/A                    N/A
-   :pypi:`pytest-easyMPI`                           Package that supports mpi tests in pytest                                                                                                                                 Oct 21, 2020    N/A                    N/A
-   :pypi:`pytest-easyread`                          pytest plugin that makes terminal printouts of the reports easier to read                                                                                                 Nov 17, 2017    N/A                    N/A
-   :pypi:`pytest-easy-server`                       Pytest plugin for easy testing against servers                                                                                                                            May 01, 2021    4 - Beta               pytest (<5.0.0,>=4.3.1) ; python_version < "3.5"
-   :pypi:`pytest-ec2`                               Pytest execution on EC2 instance                                                                                                                                          Oct 22, 2019    3 - Alpha              N/A
-   :pypi:`pytest-echo`                              pytest plugin with mechanisms for echoing environment variables, package version and generic attributes                                                                   Jan 08, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-elasticsearch`                     Elasticsearch fixtures and fixture factories for Pytest.                                                                                                                  May 12, 2021    5 - Production/Stable  pytest (>=3.0.0)
-   :pypi:`pytest-elements`                          Tool to help automate user interfaces                                                                                                                                     Jan 13, 2021    N/A                    pytest (>=5.4,<6.0)
-   :pypi:`pytest-elk-reporter`                      A simple plugin to use with pytest                                                                                                                                        Jan 24, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-email`                             Send execution result email                                                                                                                                               Jul 08, 2020    N/A                    pytest
-   :pypi:`pytest-embedded`                          pytest embedded plugin                                                                                                                                                    Nov 29, 2021    N/A                    pytest (>=6.2.0)
-   :pypi:`pytest-embedded-idf`                      pytest embedded plugin for esp-idf project                                                                                                                                Nov 29, 2021    N/A                    N/A
-   :pypi:`pytest-embedded-jtag`                     pytest embedded plugin for testing with jtag                                                                                                                              Nov 29, 2021    N/A                    N/A
-   :pypi:`pytest-embedded-qemu`                     pytest embedded plugin for qemu, not target chip                                                                                                                          Nov 29, 2021    N/A                    N/A
-   :pypi:`pytest-embedded-qemu-idf`                 pytest embedded plugin for esp-idf project by qemu, not target chip                                                                                                       Jun 29, 2021    N/A                    N/A
-   :pypi:`pytest-embedded-serial`                   pytest embedded plugin for testing serial ports                                                                                                                           Nov 29, 2021    N/A                    N/A
-   :pypi:`pytest-embedded-serial-esp`               pytest embedded plugin for testing espressif boards via serial ports                                                                                                      Nov 29, 2021    N/A                    N/A
-   :pypi:`pytest-emoji`                             A pytest plugin that adds emojis to your test result report                                                                                                               Feb 19, 2019    4 - Beta               pytest (>=4.2.1)
-   :pypi:`pytest-emoji-output`                      Pytest plugin to represent test output with emoji support                                                                                                                 Oct 10, 2021    4 - Beta               pytest (==6.0.1)
-   :pypi:`pytest-enabler`                           Enable installed pytest plugins                                                                                                                                           Nov 08, 2021    5 - Production/Stable  pytest (>=6) ; extra == 'testing'
-   :pypi:`pytest-encode`                            set your encoding and logger                                                                                                                                              Nov 06, 2021    N/A                    N/A
-   :pypi:`pytest-encode-kane`                       set your encoding and logger                                                                                                                                              Nov 16, 2021    N/A                    pytest
-   :pypi:`pytest-enhancements`                      Improvements for pytest (rejected upstream)                                                                                                                               Oct 30, 2019    4 - Beta               N/A
-   :pypi:`pytest-env`                               py.test plugin that allows you to add environment variables.                                                                                                              Jun 16, 2017    4 - Beta               N/A
-   :pypi:`pytest-envfiles`                          A py.test plugin that parses environment files before running tests                                                                                                       Oct 08, 2015    3 - Alpha              N/A
-   :pypi:`pytest-env-info`                          Push information about the running pytest into envvars                                                                                                                    Nov 25, 2017    4 - Beta               pytest (>=3.1.1)
-   :pypi:`pytest-envraw`                            py.test plugin that allows you to add environment variables.                                                                                                              Aug 27, 2020    4 - Beta               pytest (>=2.6.0)
-   :pypi:`pytest-envvars`                           Pytest plugin to validate use of envvars on your tests                                                                                                                    Jun 13, 2020    5 - Production/Stable  pytest (>=3.0.0)
-   :pypi:`pytest-env-yaml`                                                                                                                                                                                                    Apr 02, 2019    N/A                    N/A
-   :pypi:`pytest-eradicate`                         pytest plugin to check for commented out code                                                                                                                             Sep 08, 2020    N/A                    pytest (>=2.4.2)
-   :pypi:`pytest-error-for-skips`                   Pytest plugin to treat skipped tests a test failure                                                                                                                       Dec 19, 2019    4 - Beta               pytest (>=4.6)
-   :pypi:`pytest-eth`                               PyTest plugin for testing Smart Contracts for Ethereum Virtual Machine (EVM).                                                                                             Aug 14, 2020    1 - Planning           N/A
-   :pypi:`pytest-ethereum`                          pytest-ethereum: Pytest library for ethereum projects.                                                                                                                    Jun 24, 2019    3 - Alpha              pytest (==3.3.2); extra == 'dev'
-   :pypi:`pytest-eucalyptus`                        Pytest Plugin for BDD                                                                                                                                                     Aug 13, 2019    N/A                    pytest (>=4.2.0)
-   :pypi:`pytest-eventlet`                          Applies eventlet monkey-patch as a pytest plugin.                                                                                                                         Oct 04, 2021    N/A                    pytest ; extra == 'dev'
-   :pypi:`pytest-excel`                             pytest plugin for generating excel reports                                                                                                                                Oct 06, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-exceptional`                       Better exceptions                                                                                                                                                         Mar 16, 2017    4 - Beta               N/A
-   :pypi:`pytest-exception-script`                  Walk your code through exception script to check it's resiliency to failures.                                                                                             Aug 04, 2020    3 - Alpha              pytest
-   :pypi:`pytest-executable`                        pytest plugin for testing executables                                                                                                                                     Nov 10, 2021    4 - Beta               pytest (<6.3,>=4.3)
-   :pypi:`pytest-expect`                            py.test plugin to store test expectations and mark tests based on them                                                                                                    Apr 21, 2016    4 - Beta               N/A
-   :pypi:`pytest-expecter`                          Better testing with expecter and pytest.                                                                                                                                  Jul 08, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-expectr`                           This plugin is used to expect multiple assert using pytest framework.                                                                                                     Oct 05, 2018    N/A                    pytest (>=2.4.2)
-   :pypi:`pytest-explicit`                          A Pytest plugin to ignore certain marked tests by default                                                                                                                 Jun 15, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-exploratory`                       Interactive console for pytest.                                                                                                                                           Aug 03, 2021    N/A                    pytest (>=5.3)
-   :pypi:`pytest-external-blockers`                 a special outcome for tests that are blocked for external reasons                                                                                                         Oct 05, 2021    N/A                    pytest
-   :pypi:`pytest-extra-durations`                   A pytest plugin to get durations on a per-function basis and per module basis.                                                                                            Apr 21, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-fabric`                            Provides test utilities to run fabric task tests by using docker containers                                                                                               Sep 12, 2018    5 - Production/Stable  N/A
-   :pypi:`pytest-factory`                           Use factories for test setup with py.test                                                                                                                                 Sep 06, 2020    3 - Alpha              pytest (>4.3)
-   :pypi:`pytest-factoryboy`                        Factory Boy support for pytest.                                                                                                                                           Dec 30, 2020    6 - Mature             pytest (>=4.6)
-   :pypi:`pytest-factoryboy-fixtures`               Generates pytest fixtures that allow the use of type hinting                                                                                                              Jun 25, 2020    N/A                    N/A
-   :pypi:`pytest-factoryboy-state`                  Simple factoryboy random state management                                                                                                                                 Dec 11, 2020    4 - Beta               pytest (>=5.0)
-   :pypi:`pytest-failed-screenshot`                 Test case fails,take a screenshot,save it,attach it to the allure                                                                                                         Apr 21, 2021    N/A                    N/A
-   :pypi:`pytest-failed-to-verify`                  A pytest plugin that helps better distinguishing real test failures from setup flakiness.                                                                                 Aug 08, 2019    5 - Production/Stable  pytest (>=4.1.0)
-   :pypi:`pytest-faker`                             Faker integration with the pytest framework.                                                                                                                              Dec 19, 2016    6 - Mature             N/A
-   :pypi:`pytest-falcon`                            Pytest helpers for Falcon.                                                                                                                                                Sep 07, 2016    4 - Beta               N/A
-   :pypi:`pytest-falcon-client`                     Pytest \`client\` fixture for the Falcon Framework                                                                                                                        Mar 19, 2019    N/A                    N/A
-   :pypi:`pytest-fantasy`                           Pytest plugin for Flask Fantasy Framework                                                                                                                                 Mar 14, 2019    N/A                    N/A
-   :pypi:`pytest-fastapi`                                                                                                                                                                                                     Dec 27, 2020    N/A                    N/A
-   :pypi:`pytest-fastest`                           Use SCM and coverage to run only needed tests                                                                                                                             Mar 05, 2020    N/A                    N/A
-   :pypi:`pytest-fast-first`                        Pytest plugin that runs fast tests first                                                                                                                                  Apr 02, 2021    3 - Alpha              pytest
-   :pypi:`pytest-faulthandler`                      py.test plugin that activates the fault handler module for tests (dummy package)                                                                                          Jul 04, 2019    6 - Mature             pytest (>=5.0)
-   :pypi:`pytest-fauxfactory`                       Integration of fauxfactory into pytest.                                                                                                                                   Dec 06, 2017    5 - Production/Stable  pytest (>=3.2)
-   :pypi:`pytest-figleaf`                           py.test figleaf coverage plugin                                                                                                                                           Jan 18, 2010    5 - Production/Stable  N/A
-   :pypi:`pytest-filecov`                           A pytest plugin to detect unused files                                                                                                                                    Jun 27, 2021    4 - Beta               pytest
-   :pypi:`pytest-filedata`                          easily load data from files                                                                                                                                               Jan 17, 2019    4 - Beta               N/A
-   :pypi:`pytest-filemarker`                        A pytest plugin that runs marked tests when files change.                                                                                                                 Dec 01, 2020    N/A                    pytest
-   :pypi:`pytest-filter-case`                       run test cases filter by mark                                                                                                                                             Nov 05, 2020    N/A                    N/A
-   :pypi:`pytest-filter-subpackage`                 Pytest plugin for filtering based on sub-packages                                                                                                                         Jan 09, 2020    3 - Alpha              pytest (>=3.0)
-   :pypi:`pytest-find-dependencies`                 A pytest plugin to find dependencies between tests                                                                                                                        Apr 21, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-finer-verdicts`                    A pytest plugin to treat non-assertion failures as test errors.                                                                                                           Jun 18, 2020    N/A                    pytest (>=5.4.3)
-   :pypi:`pytest-firefox`                           pytest plugin to manipulate firefox                                                                                                                                       Aug 08, 2017    3 - Alpha              pytest (>=3.0.2)
-   :pypi:`pytest-fixture-config`                    Fixture configuration utils for py.test                                                                                                                                   May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-fixture-maker`                     Pytest plugin to load fixtures from YAML files                                                                                                                            Sep 21, 2021    N/A                    N/A
-   :pypi:`pytest-fixture-marker`                    A pytest plugin to add markers based on fixtures used.                                                                                                                    Oct 11, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-fixture-order`                     pytest plugin to control fixture evaluation order                                                                                                                         Aug 25, 2020    N/A                    pytest (>=3.0)
-   :pypi:`pytest-fixtures`                          Common fixtures for pytest                                                                                                                                                May 01, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-fixture-tools`                     Plugin for pytest which provides tools for fixtures                                                                                                                       Aug 18, 2020    6 - Mature             pytest
-   :pypi:`pytest-fixture-typecheck`                 A pytest plugin to assert type annotations at runtime.                                                                                                                    Aug 24, 2021    N/A                    pytest
-   :pypi:`pytest-flake8`                            pytest plugin to check FLAKE8 requirements                                                                                                                                Dec 16, 2020    4 - Beta               pytest (>=3.5)
-   :pypi:`pytest-flake8-path`                       A pytest fixture for testing flake8 plugins.                                                                                                                              Aug 11, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-flakefinder`                       Runs tests multiple times to expose flakiness.                                                                                                                            Jul 28, 2020    4 - Beta               pytest (>=2.7.1)
-   :pypi:`pytest-flakes`                            pytest plugin to check source code with pyflakes                                                                                                                          Dec 02, 2021    5 - Production/Stable  pytest (>=5)
-   :pypi:`pytest-flaptastic`                        Flaptastic py.test plugin                                                                                                                                                 Mar 17, 2019    N/A                    N/A
-   :pypi:`pytest-flask`                             A set of py.test fixtures to test Flask applications.                                                                                                                     Feb 27, 2021    5 - Production/Stable  pytest (>=5.2)
-   :pypi:`pytest-flask-sqlalchemy`                  A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions.                                                                            Apr 04, 2019    4 - Beta               pytest (>=3.2.1)
-   :pypi:`pytest-flask-sqlalchemy-transactions`     Run tests in transactions using pytest, Flask, and SQLalchemy.                                                                                                            Aug 02, 2018    4 - Beta               pytest (>=3.2.1)
-   :pypi:`pytest-flyte`                             Pytest fixtures for simplifying Flyte integration testing                                                                                                                 May 03, 2021    N/A                    pytest
-   :pypi:`pytest-focus`                             A pytest plugin that alerts user of failed test cases with screen notifications                                                                                           May 04, 2019    4 - Beta               pytest
-   :pypi:`pytest-forcefail`                         py.test plugin to make the test failing regardless of pytest.mark.xfail                                                                                                   May 15, 2018    4 - Beta               N/A
-   :pypi:`pytest-forward-compatability`             A name to avoid typosquating pytest-foward-compatibility                                                                                                                  Sep 06, 2020    N/A                    N/A
-   :pypi:`pytest-forward-compatibility`             A pytest plugin to shim pytest commandline options for fowards compatibility                                                                                              Sep 29, 2020    N/A                    N/A
-   :pypi:`pytest-freezegun`                         Wrap tests with fixtures in freeze_time                                                                                                                                   Jul 19, 2020    4 - Beta               pytest (>=3.0.0)
-   :pypi:`pytest-freeze-reqs`                       Check if requirement files are frozen                                                                                                                                     Apr 29, 2021    N/A                    N/A
-   :pypi:`pytest-frozen-uuids`                      Deterministically frozen UUID's for your tests                                                                                                                            Oct 19, 2021    N/A                    pytest (>=3.0)
-   :pypi:`pytest-func-cov`                          Pytest plugin for measuring function coverage                                                                                                                             Apr 15, 2021    3 - Alpha              pytest (>=5)
-   :pypi:`pytest-funparam`                          An alternative way to parametrize test cases.                                                                                                                             Dec 02, 2021    4 - Beta               pytest >=4.6.0
-   :pypi:`pytest-fxa`                               pytest plugin for Firefox Accounts                                                                                                                                        Aug 28, 2018    5 - Production/Stable  N/A
-   :pypi:`pytest-fxtest`                                                                                                                                                                                                      Oct 27, 2020    N/A                    N/A
-   :pypi:`pytest-gc`                                The garbage collector plugin for py.test                                                                                                                                  Feb 01, 2018    N/A                    N/A
-   :pypi:`pytest-gcov`                              Uses gcov to measure test coverage of a C library                                                                                                                         Feb 01, 2018    3 - Alpha              N/A
-   :pypi:`pytest-gevent`                            Ensure that gevent is properly patched when invoking pytest                                                                                                               Feb 25, 2020    N/A                    pytest
-   :pypi:`pytest-gherkin`                           A flexible framework for executing BDD gherkin tests                                                                                                                      Jul 27, 2019    3 - Alpha              pytest (>=5.0.0)
-   :pypi:`pytest-ghostinspector`                    For finding/executing Ghost Inspector tests                                                                                                                               May 17, 2016    3 - Alpha              N/A
-   :pypi:`pytest-girder`                            A set of pytest fixtures for testing Girder applications.                                                                                                                 Nov 30, 2021    N/A                    N/A
-   :pypi:`pytest-git`                               Git repository fixture for py.test                                                                                                                                        May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-gitcov`                            Pytest plugin for reporting on coverage of the last git commit.                                                                                                           Jan 11, 2020    2 - Pre-Alpha          N/A
-   :pypi:`pytest-git-fixtures`                      Pytest fixtures for testing with git.                                                                                                                                     Mar 11, 2021    4 - Beta               pytest
-   :pypi:`pytest-github`                            Plugin for py.test that associates tests with github issues using a marker.                                                                                               Mar 07, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-github-actions-annotate-failures`  pytest plugin to annotate failed tests with a workflow command for GitHub Actions                                                                                         Oct 24, 2021    N/A                    pytest (>=4.0.0)
-   :pypi:`pytest-gitignore`                         py.test plugin to ignore the same files as git                                                                                                                            Jul 17, 2015    4 - Beta               N/A
-   :pypi:`pytest-glamor-allure`                     Extends allure-pytest functionality                                                                                                                                       Nov 26, 2021    4 - Beta               pytest
-   :pypi:`pytest-gnupg-fixtures`                    Pytest fixtures for testing with gnupg.                                                                                                                                   Mar 04, 2021    4 - Beta               pytest
-   :pypi:`pytest-golden`                            Plugin for pytest that offloads expected outputs to data files                                                                                                            Nov 23, 2020    N/A                    pytest (>=6.1.2,<7.0.0)
-   :pypi:`pytest-graphql-schema`                    Get graphql schema as fixture for pytest                                                                                                                                  Oct 18, 2019    N/A                    N/A
-   :pypi:`pytest-greendots`                         Green progress dots                                                                                                                                                       Feb 08, 2014    3 - Alpha              N/A
-   :pypi:`pytest-growl`                             Growl notifications for pytest results.                                                                                                                                   Jan 13, 2014    5 - Production/Stable  N/A
-   :pypi:`pytest-grpc`                              pytest plugin for grpc                                                                                                                                                    May 01, 2020    N/A                    pytest (>=3.6.0)
-   :pypi:`pytest-hammertime`                        Display "🔨 " instead of "." for passed pytest tests.                                                                                                                     Jul 28, 2018    N/A                    pytest
-   :pypi:`pytest-harvest`                           Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes.                             Apr 01, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-helm-chart`                        A plugin to provide different types and configs of Kubernetes clusters that can be used for testing.                                                                      Jun 15, 2020    4 - Beta               pytest (>=5.4.2,<6.0.0)
-   :pypi:`pytest-helm-charts`                       A plugin to provide different types and configs of Kubernetes clusters that can be used for testing.                                                                      Oct 26, 2021    4 - Beta               pytest (>=6.1.2,<7.0.0)
-   :pypi:`pytest-helper`                            Functions to help in using the pytest testing framework                                                                                                                   May 31, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-helpers`                           pytest helpers                                                                                                                                                            May 17, 2020    N/A                    pytest
-   :pypi:`pytest-helpers-namespace`                 Pytest Helpers Namespace Plugin                                                                                                                                           Apr 29, 2021    5 - Production/Stable  pytest (>=6.0.0)
-   :pypi:`pytest-hidecaptured`                      Hide captured output                                                                                                                                                      May 04, 2018    4 - Beta               pytest (>=2.8.5)
-   :pypi:`pytest-historic`                          Custom report to display pytest historical execution records                                                                                                              Apr 08, 2020    N/A                    pytest
-   :pypi:`pytest-historic-hook`                     Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report                                                                        Apr 08, 2020    N/A                    pytest
-   :pypi:`pytest-homeassistant`                     A pytest plugin for use with homeassistant custom components.                                                                                                             Aug 12, 2020    4 - Beta               N/A
-   :pypi:`pytest-homeassistant-custom-component`    Experimental package to automatically extract test plugins for Home Assistant custom components                                                                           Nov 20, 2021    3 - Alpha              pytest (==6.2.5)
-   :pypi:`pytest-honors`                            Report on tests that honor constraints, and guard against regressions                                                                                                     Mar 06, 2020    4 - Beta               N/A
-   :pypi:`pytest-hoverfly`                          Simplify working with Hoverfly from pytest                                                                                                                                Jul 12, 2021    N/A                    pytest (>=5.0)
-   :pypi:`pytest-hoverfly-wrapper`                  Integrates the Hoverfly HTTP proxy into Pytest                                                                                                                            Aug 29, 2021    4 - Beta               N/A
-   :pypi:`pytest-hpfeeds`                           Helpers for testing hpfeeds in your python project                                                                                                                        Aug 27, 2021    4 - Beta               pytest (>=6.2.4,<7.0.0)
-   :pypi:`pytest-html`                              pytest plugin for generating HTML reports                                                                                                                                 Dec 13, 2020    5 - Production/Stable  pytest (!=6.0.0,>=5.0)
-   :pypi:`pytest-html-lee`                          optimized pytest plugin for generating HTML reports                                                                                                                       Jun 30, 2020    5 - Production/Stable  pytest (>=5.0)
-   :pypi:`pytest-html-profiling`                    Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt.                            Feb 11, 2020    5 - Production/Stable  pytest (>=3.0)
-   :pypi:`pytest-html-reporter`                     Generates a static html report based on pytest framework                                                                                                                  Apr 25, 2021    N/A                    N/A
-   :pypi:`pytest-html-thread`                       pytest plugin for generating HTML reports                                                                                                                                 Dec 29, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-http`                              Fixture "http" for http requests                                                                                                                                          Dec 05, 2019    N/A                    N/A
-   :pypi:`pytest-httpbin`                           Easily test your HTTP library against a local copy of httpbin                                                                                                             Feb 11, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-http-mocker`                       Pytest plugin for http mocking (via https://github.com/vilus/mocker)                                                                                                      Oct 20, 2019    N/A                    N/A
-   :pypi:`pytest-httpretty`                         A thin wrapper of HTTPretty for pytest                                                                                                                                    Feb 16, 2014    3 - Alpha              N/A
-   :pypi:`pytest-httpserver`                        pytest-httpserver is a httpserver for pytest                                                                                                                              Oct 18, 2021    3 - Alpha              pytest ; extra == 'dev'
-   :pypi:`pytest-httpx`                             Send responses to httpx.                                                                                                                                                  Nov 16, 2021    5 - Production/Stable  pytest (==6.*)
-   :pypi:`pytest-httpx-blockage`                    Disable httpx requests during a test run                                                                                                                                  Nov 16, 2021    N/A                    pytest (>=6.2.5)
-   :pypi:`pytest-hue`                               Visualise PyTest status via your Phillips Hue lights                                                                                                                      May 09, 2019    N/A                    N/A
-   :pypi:`pytest-hylang`                            Pytest plugin to allow running tests written in hylang                                                                                                                    Mar 28, 2021    N/A                    pytest
-   :pypi:`pytest-hypo-25`                           help hypo module for pytest                                                                                                                                               Jan 12, 2020    3 - Alpha              N/A
-   :pypi:`pytest-ibutsu`                            A plugin to sent pytest results to an Ibutsu server                                                                                                                       Jun 16, 2021    4 - Beta               pytest
-   :pypi:`pytest-icdiff`                            use icdiff for better error messages in pytest assertions                                                                                                                 Apr 08, 2020    4 - Beta               N/A
-   :pypi:`pytest-idapro`                            A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api  Nov 03, 2018    N/A                    N/A
-   :pypi:`pytest-idempotent`                        Pytest plugin for testing function idempotence.                                                                                                                           Nov 26, 2021    N/A                    N/A
-   :pypi:`pytest-ignore-flaky`                      ignore failures from flaky tests (pytest plugin)                                                                                                                          Apr 23, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-image-diff`                                                                                                                                                                                                  Jul 28, 2021    3 - Alpha              pytest
-   :pypi:`pytest-incremental`                       an incremental test runner (pytest plugin)                                                                                                                                Apr 24, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-influxdb`                          Plugin for influxdb and pytest integration.                                                                                                                               Apr 20, 2021    N/A                    N/A
-   :pypi:`pytest-info-collector`                    pytest plugin to collect information from tests                                                                                                                           May 26, 2019    3 - Alpha              N/A
-   :pypi:`pytest-informative-node`                  display more node ininformation.                                                                                                                                          Apr 25, 2019    4 - Beta               N/A
-   :pypi:`pytest-infrastructure`                    pytest stack validation prior to testing executing                                                                                                                        Apr 12, 2020    4 - Beta               N/A
-   :pypi:`pytest-ini`                               Reuse pytest.ini to store env variables                                                                                                                                   Sep 30, 2021    N/A                    N/A
-   :pypi:`pytest-inmanta`                           A py.test plugin providing fixtures to simplify inmanta modules testing.                                                                                                  Aug 17, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-inmanta-extensions`                Inmanta tests package                                                                                                                                                     May 27, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-Inomaly`                           A simple image diff plugin for pytest                                                                                                                                     Feb 13, 2018    4 - Beta               N/A
-   :pypi:`pytest-insta`                             A practical snapshot testing plugin for pytest                                                                                                                            Apr 07, 2021    N/A                    pytest (>=6.0.2,<7.0.0)
-   :pypi:`pytest-instafail`                         pytest plugin to show failures instantly                                                                                                                                  Jun 14, 2020    4 - Beta               pytest (>=2.9)
-   :pypi:`pytest-instrument`                        pytest plugin to instrument tests                                                                                                                                         Apr 05, 2020    5 - Production/Stable  pytest (>=5.1.0)
-   :pypi:`pytest-integration`                       Organizing pytests by integration or not                                                                                                                                  Apr 16, 2020    N/A                    N/A
-   :pypi:`pytest-integration-mark`                  Automatic integration test marking and excluding plugin for pytest                                                                                                        Jul 19, 2021    N/A                    pytest (>=5.2,<7.0)
-   :pypi:`pytest-interactive`                       A pytest plugin for console based interactive test selection just after the collection phase                                                                              Nov 30, 2017    3 - Alpha              N/A
-   :pypi:`pytest-intercept-remote`                  Pytest plugin for intercepting outgoing connection requests during pytest run.                                                                                            May 24, 2021    4 - Beta               pytest (>=4.6)
-   :pypi:`pytest-invenio`                           Pytest fixtures for Invenio.                                                                                                                                              May 11, 2021    5 - Production/Stable  pytest (<7,>=6)
-   :pypi:`pytest-involve`                           Run tests covering a specific file or changeset                                                                                                                           Feb 02, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-ipdb`                              A py.test plug-in to enable drop to ipdb debugger on test failure.                                                                                                        Sep 02, 2014    2 - Pre-Alpha          N/A
-   :pypi:`pytest-ipynb`                             THIS PROJECT IS ABANDONED                                                                                                                                                 Jan 29, 2019    3 - Alpha              N/A
-   :pypi:`pytest-isort`                             py.test plugin to check import ordering using isort                                                                                                                       Apr 27, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-it`                                Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it.                                                      Jan 22, 2020    4 - Beta               N/A
-   :pypi:`pytest-iterassert`                        Nicer list and iterable assertion messages for pytest                                                                                                                     May 11, 2020    3 - Alpha              N/A
-   :pypi:`pytest-jasmine`                           Run jasmine tests from your pytest test suite                                                                                                                             Nov 04, 2017    1 - Planning           N/A
-   :pypi:`pytest-jest`                              A custom jest-pytest oriented Pytest reporter                                                                                                                             May 22, 2018    4 - Beta               pytest (>=3.3.2)
-   :pypi:`pytest-jira`                              py.test JIRA integration plugin, using markers                                                                                                                            Dec 02, 2021    3 - Alpha              N/A
-   :pypi:`pytest-jira-xray`                         pytest plugin to integrate tests with JIRA XRAY                                                                                                                           Nov 28, 2021    3 - Alpha              pytest
-   :pypi:`pytest-jobserver`                         Limit parallel tests with posix jobserver.                                                                                                                                May 15, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-joke`                              Test failures are better served with humor.                                                                                                                               Oct 08, 2019    4 - Beta               pytest (>=4.2.1)
-   :pypi:`pytest-json`                              Generate JSON test reports                                                                                                                                                Jan 18, 2016    4 - Beta               N/A
-   :pypi:`pytest-jsonlint`                          UNKNOWN                                                                                                                                                                   Aug 04, 2016    N/A                    N/A
-   :pypi:`pytest-json-report`                       A pytest plugin to report test results as JSON files                                                                                                                      Sep 24, 2021    4 - Beta               pytest (>=3.8.0)
-   :pypi:`pytest-kafka`                             Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest                                                                                                           Aug 24, 2021    N/A                    pytest
-   :pypi:`pytest-kafkavents`                        A plugin to send pytest events to Kafka                                                                                                                                   Sep 08, 2021    4 - Beta               pytest
-   :pypi:`pytest-kind`                              Kubernetes test support with KIND for pytest                                                                                                                              Jan 24, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-kivy`                              Kivy GUI tests fixtures using pytest                                                                                                                                      Jul 06, 2021    4 - Beta               pytest (>=3.6)
-   :pypi:`pytest-knows`                             A pytest plugin that can automaticly skip test case based on dependence info calculated by trace                                                                          Aug 22, 2014    N/A                    N/A
-   :pypi:`pytest-konira`                            Run Konira DSL tests with py.test                                                                                                                                         Oct 09, 2011    N/A                    N/A
-   :pypi:`pytest-krtech-common`                     pytest krtech common library                                                                                                                                              Nov 28, 2016    4 - Beta               N/A
-   :pypi:`pytest-kwparametrize`                     Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks                                                                 Jan 22, 2021    N/A                    pytest (>=6)
-   :pypi:`pytest-lambda`                            Define pytest fixtures with lambda functions.                                                                                                                             Aug 23, 2021    3 - Alpha              pytest (>=3.6,<7)
-   :pypi:`pytest-lamp`                                                                                                                                                                                                        Jan 06, 2017    3 - Alpha              N/A
-   :pypi:`pytest-layab`                             Pytest fixtures for layab.                                                                                                                                                Oct 05, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-lazy-fixture`                      It helps to use fixtures in pytest.mark.parametrize                                                                                                                       Feb 01, 2020    4 - Beta               pytest (>=3.2.5)
-   :pypi:`pytest-ldap`                              python-ldap fixtures for pytest                                                                                                                                           Aug 18, 2020    N/A                    pytest
-   :pypi:`pytest-leaks`                             A pytest plugin to trace resource leaks.                                                                                                                                  Nov 27, 2019    1 - Planning           N/A
-   :pypi:`pytest-level`                             Select tests of a given level or lower                                                                                                                                    Oct 21, 2019    N/A                    pytest
-   :pypi:`pytest-libfaketime`                       A python-libfaketime plugin for pytest.                                                                                                                                   Dec 22, 2018    4 - Beta               pytest (>=3.0.0)
-   :pypi:`pytest-libiio`                            A pytest plugin to manage interfacing with libiio contexts                                                                                                                Oct 29, 2021    4 - Beta               N/A
-   :pypi:`pytest-libnotify`                         Pytest plugin that shows notifications about the test run                                                                                                                 Apr 02, 2021    3 - Alpha              pytest
-   :pypi:`pytest-ligo`                                                                                                                                                                                                        Jan 16, 2020    4 - Beta               N/A
-   :pypi:`pytest-lineno`                            A pytest plugin to show the line numbers of test functions                                                                                                                Dec 04, 2020    N/A                    pytest
-   :pypi:`pytest-line-profiler`                     Profile code executed by pytest                                                                                                                                           May 03, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-lisa`                              Pytest plugin for organizing tests.                                                                                                                                       Jan 21, 2021    3 - Alpha              pytest (>=6.1.2,<7.0.0)
-   :pypi:`pytest-listener`                          A simple network listener                                                                                                                                                 May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-litf`                              A pytest plugin that stream output in LITF format                                                                                                                         Jan 18, 2021    4 - Beta               pytest (>=3.1.1)
-   :pypi:`pytest-live`                              Live results for pytest                                                                                                                                                   Mar 08, 2020    N/A                    pytest
-   :pypi:`pytest-localftpserver`                    A PyTest plugin which provides an FTP fixture for your tests                                                                                                              Aug 25, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-localserver`                       py.test plugin to test server connections locally.                                                                                                                        Nov 19, 2021    4 - Beta               N/A
-   :pypi:`pytest-localstack`                        Pytest plugin for AWS integration tests                                                                                                                                   Aug 22, 2019    4 - Beta               pytest (>=3.3.0)
-   :pypi:`pytest-lockable`                          lockable resource plugin for pytest                                                                                                                                       Nov 09, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-locker`                            Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed                                               Oct 29, 2021    N/A                    pytest (>=5.4)
-   :pypi:`pytest-log`                               print log                                                                                                                                                                 Aug 15, 2021    N/A                    pytest (>=3.8)
-   :pypi:`pytest-logbook`                           py.test plugin to capture logbook log messages                                                                                                                            Nov 23, 2015    5 - Production/Stable  pytest (>=2.8)
-   :pypi:`pytest-logdog`                            Pytest plugin to test logging                                                                                                                                             Jun 15, 2021    1 - Planning           pytest (>=6.2.0)
-   :pypi:`pytest-logfest`                           Pytest plugin providing three logger fixtures with basic or full writing to log files                                                                                     Jul 21, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-logger`                            Plugin configuring handlers for loggers from Python logging module.                                                                                                       Jul 25, 2019    4 - Beta               pytest (>=3.2)
-   :pypi:`pytest-logging`                           Configures logging and allows tweaking the log level with a py.test flag                                                                                                  Nov 04, 2015    4 - Beta               N/A
-   :pypi:`pytest-log-report`                        Package for creating a pytest test run reprot                                                                                                                             Dec 26, 2019    N/A                    N/A
-   :pypi:`pytest-manual-marker`                     pytest marker for marking manual tests                                                                                                                                    Oct 11, 2021    3 - Alpha              pytest (>=6)
-   :pypi:`pytest-markdown`                          Test your markdown docs with pytest                                                                                                                                       Jan 15, 2021    4 - Beta               pytest (>=6.0.1,<7.0.0)
-   :pypi:`pytest-marker-bugzilla`                   py.test bugzilla integration plugin, using markers                                                                                                                        Jan 09, 2020    N/A                    N/A
-   :pypi:`pytest-markers-presence`                  A simple plugin to detect missed pytest tags and markers"                                                                                                                 Feb 04, 2021    4 - Beta               pytest (>=6.0)
-   :pypi:`pytest-markfiltration`                    UNKNOWN                                                                                                                                                                   Nov 08, 2011    3 - Alpha              N/A
-   :pypi:`pytest-mark-no-py3`                       pytest plugin and bowler codemod to help migrate tests to Python 3                                                                                                        May 17, 2019    N/A                    pytest
-   :pypi:`pytest-marks`                             UNKNOWN                                                                                                                                                                   Nov 23, 2012    3 - Alpha              N/A
-   :pypi:`pytest-matcher`                           Match test output against patterns stored in files                                                                                                                        Apr 23, 2020    5 - Production/Stable  pytest (>=3.4)
-   :pypi:`pytest-match-skip`                        Skip matching marks. Matches partial marks using wildcards.                                                                                                               May 15, 2019    4 - Beta               pytest (>=4.4.1)
-   :pypi:`pytest-mat-report`                        this is report                                                                                                                                                            Jan 20, 2021    N/A                    N/A
-   :pypi:`pytest-matrix`                            Provide tools for generating tests from combinations of fixtures.                                                                                                         Jun 24, 2020    5 - Production/Stable  pytest (>=5.4.3,<6.0.0)
-   :pypi:`pytest-mccabe`                            pytest plugin to run the mccabe code complexity checker.                                                                                                                  Jul 22, 2020    3 - Alpha              pytest (>=5.4.0)
-   :pypi:`pytest-md`                                Plugin for generating Markdown reports for pytest results                                                                                                                 Jul 11, 2019    3 - Alpha              pytest (>=4.2.1)
-   :pypi:`pytest-md-report`                         A pytest plugin to make a test results report with Markdown table format.                                                                                                 May 04, 2021    4 - Beta               pytest (!=6.0.0,<7,>=3.3.2)
-   :pypi:`pytest-memprof`                           Estimates memory consumption of test functions                                                                                                                            Mar 29, 2019    4 - Beta               N/A
-   :pypi:`pytest-menu`                              A pytest plugin for console based interactive test selection just after the collection phase                                                                              Oct 04, 2017    3 - Alpha              pytest (>=2.4.2)
-   :pypi:`pytest-mercurial`                         pytest plugin to write integration tests for projects using Mercurial Python internals                                                                                    Nov 21, 2020    1 - Planning           N/A
-   :pypi:`pytest-message`                           Pytest plugin for sending report message of marked tests execution                                                                                                        Nov 04, 2021    N/A                    pytest (>=6.2.5)
-   :pypi:`pytest-messenger`                         Pytest to Slack reporting plugin                                                                                                                                          Dec 16, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-metadata`                          pytest plugin for test session metadata                                                                                                                                   Nov 27, 2020    5 - Production/Stable  pytest (>=2.9.0)
-   :pypi:`pytest-metrics`                           Custom metrics report for pytest                                                                                                                                          Apr 04, 2020    N/A                    pytest
-   :pypi:`pytest-mimesis`                           Mimesis integration with the pytest test runner                                                                                                                           Mar 21, 2020    5 - Production/Stable  pytest (>=4.2)
-   :pypi:`pytest-minecraft`                         A pytest plugin for running tests against Minecraft releases                                                                                                              Sep 26, 2020    N/A                    pytest (>=6.0.1,<7.0.0)
-   :pypi:`pytest-missing-fixtures`                  Pytest plugin that creates missing fixtures                                                                                                                               Oct 14, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-ml`                                Test your machine learning!                                                                                                                                               May 04, 2019    4 - Beta               N/A
-   :pypi:`pytest-mocha`                             pytest plugin to display test execution output like a mochajs                                                                                                             Apr 02, 2020    4 - Beta               pytest (>=5.4.0)
-   :pypi:`pytest-mock`                              Thin-wrapper around the mock package for easier use with pytest                                                                                                           May 06, 2021    5 - Production/Stable  pytest (>=5.0)
-   :pypi:`pytest-mock-api`                          A mock API server with configurable routes and responses available as a fixture.                                                                                          Feb 13, 2019    1 - Planning           pytest (>=4.0.0)
-   :pypi:`pytest-mock-generator`                    A pytest fixture wrapper for https://pypi.org/project/mock-generator                                                                                                      Aug 10, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-mock-helper`                       Help you mock HTTP call and generate mock code                                                                                                                            Jan 24, 2018    N/A                    pytest
-   :pypi:`pytest-mockito`                           Base fixtures for mockito                                                                                                                                                 Jul 11, 2018    4 - Beta               N/A
-   :pypi:`pytest-mockredis`                         An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database.                                      Jan 02, 2018    2 - Pre-Alpha          N/A
-   :pypi:`pytest-mock-resources`                    A pytest plugin for easily instantiating reproducible mock resources.                                                                                                     Dec 03, 2021    N/A                    pytest (>=1.0)
-   :pypi:`pytest-mock-server`                       Mock server plugin for pytest                                                                                                                                             Apr 06, 2020    4 - Beta               N/A
-   :pypi:`pytest-mockservers`                       A set of fixtures to test your requests to HTTP/UDP servers                                                                                                               Mar 31, 2020    N/A                    pytest (>=4.3.0)
-   :pypi:`pytest-modifyjunit`                       Utility for adding additional properties to junit xml for IDM QE                                                                                                          Jan 10, 2019    N/A                    N/A
-   :pypi:`pytest-modifyscope`                       pytest plugin to modify fixture scope                                                                                                                                     Apr 12, 2020    N/A                    pytest
-   :pypi:`pytest-molecule`                          PyTest Molecule Plugin :: discover and run molecule tests                                                                                                                 Oct 06, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-mongo`                             MongoDB process and client fixtures plugin for Pytest.                                                                                                                    Jun 07, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-mongodb`                           pytest plugin for MongoDB fixtures                                                                                                                                        Dec 07, 2019    5 - Production/Stable  pytest (>=2.5.2)
-   :pypi:`pytest-monitor`                           Pytest plugin for analyzing resource usage.                                                                                                                               Aug 24, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-monkeyplus`                        pytest's monkeypatch subclass with extra functionalities                                                                                                                  Sep 18, 2012    5 - Production/Stable  N/A
-   :pypi:`pytest-monkeytype`                        pytest-monkeytype: Generate Monkeytype annotations from your pytest tests.                                                                                                Jul 29, 2020    4 - Beta               N/A
-   :pypi:`pytest-moto`                              Fixtures for integration tests of AWS services,uses moto mocking library.                                                                                                 Aug 28, 2015    1 - Planning           N/A
-   :pypi:`pytest-motor`                             A pytest plugin for motor, the non-blocking MongoDB driver.                                                                                                               Jul 21, 2021    3 - Alpha              pytest
-   :pypi:`pytest-mp`                                A test batcher for multiprocessed Pytest runs                                                                                                                             May 23, 2018    4 - Beta               pytest
-   :pypi:`pytest-mpi`                               pytest plugin to collect information from tests                                                                                                                           Mar 14, 2021    3 - Alpha              pytest
-   :pypi:`pytest-mpl`                               pytest plugin to help with testing figures output from Matplotlib                                                                                                         Jul 02, 2021    4 - Beta               pytest
-   :pypi:`pytest-mproc`                             low-startup-overhead, scalable, distributed-testing pytest plugin                                                                                                         Mar 07, 2021    4 - Beta               pytest
-   :pypi:`pytest-multi-check`                       Pytest-плагин, реализует возможность мульти проверок и мягких проверок                                                                                                    Jun 03, 2021    N/A                    pytest
-   :pypi:`pytest-multihost`                         Utility for writing multi-host tests for pytest                                                                                                                           Apr 07, 2020    4 - Beta               N/A
-   :pypi:`pytest-multilog`                          Multi-process logs handling and other helpers for pytest                                                                                                                  Jun 10, 2021    N/A                    N/A
-   :pypi:`pytest-multithreading`                    a pytest plugin for th and concurrent testing                                                                                                                             Aug 12, 2021    N/A                    pytest (>=3.6)
-   :pypi:`pytest-mutagen`                           Add the mutation testing feature to pytest                                                                                                                                Jul 24, 2020    N/A                    pytest (>=5.4)
-   :pypi:`pytest-mypy`                              Mypy static type checker plugin for Pytest                                                                                                                                Mar 21, 2021    4 - Beta               pytest (>=3.5)
-   :pypi:`pytest-mypyd`                             Mypy static type checker plugin for Pytest                                                                                                                                Aug 20, 2019    4 - Beta               pytest (<4.7,>=2.8) ; python_version < "3.5"
-   :pypi:`pytest-mypy-plugins`                      pytest plugin for writing tests for mypy plugins                                                                                                                          Oct 19, 2021    3 - Alpha              pytest (>=6.0.0)
-   :pypi:`pytest-mypy-plugins-shim`                 Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy.                                                                           Apr 12, 2021    N/A                    N/A
-   :pypi:`pytest-mypy-testing`                      Pytest plugin to check mypy output.                                                                                                                                       Jun 13, 2021    N/A                    pytest
-   :pypi:`pytest-mysql`                             MySQL process and client fixtures for pytest                                                                                                                              Nov 22, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-needle`                            pytest plugin for visual testing websites using selenium                                                                                                                  Dec 10, 2018    4 - Beta               pytest (<5.0.0,>=3.0.0)
-   :pypi:`pytest-neo`                               pytest-neo is a plugin for pytest that shows tests like screen of Matrix.                                                                                                 Apr 23, 2019    3 - Alpha              pytest (>=3.7.2)
-   :pypi:`pytest-network`                           A simple plugin to disable network on socket level.                                                                                                                       May 07, 2020    N/A                    N/A
-   :pypi:`pytest-never-sleep`                       pytest plugin helps to avoid adding tests without mock \`time.sleep\`                                                                                                     May 05, 2021    3 - Alpha              pytest (>=3.5.1)
-   :pypi:`pytest-nginx`                             nginx fixture for pytest                                                                                                                                                  Aug 12, 2017    5 - Production/Stable  N/A
-   :pypi:`pytest-nginx-iplweb`                      nginx fixture for pytest - iplweb temporary fork                                                                                                                          Mar 01, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-ngrok`                                                                                                                                                                                                       Jan 22, 2020    3 - Alpha              N/A
-   :pypi:`pytest-ngsfixtures`                       pytest ngs fixtures                                                                                                                                                       Sep 06, 2019    2 - Pre-Alpha          pytest (>=5.0.0)
-   :pypi:`pytest-nice`                              A pytest plugin that alerts user of failed test cases with screen notifications                                                                                           May 04, 2019    4 - Beta               pytest
-   :pypi:`pytest-nice-parametrize`                  A small snippet for nicer PyTest's Parametrize                                                                                                                            Apr 17, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-nlcov`                             Pytest plugin to get the coverage of the new lines (based on git diff) only                                                                                               Jul 07, 2021    N/A                    N/A
-   :pypi:`pytest-nocustom`                          Run all tests without custom markers                                                                                                                                      Jul 07, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-nodev`                             Test-driven source code search for Python.                                                                                                                                Jul 21, 2016    4 - Beta               pytest (>=2.8.1)
-   :pypi:`pytest-nogarbage`                         Ensure a test produces no garbage                                                                                                                                         Aug 29, 2021    5 - Production/Stable  pytest (>=4.6.0)
-   :pypi:`pytest-notebook`                          A pytest plugin for testing Jupyter Notebooks                                                                                                                             Sep 16, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-notice`                            Send pytest execution result email                                                                                                                                        Nov 05, 2020    N/A                    N/A
-   :pypi:`pytest-notification`                      A pytest plugin for sending a desktop notification and playing a sound upon completion of tests                                                                           Jun 19, 2020    N/A                    pytest (>=4)
-   :pypi:`pytest-notifier`                          A pytest plugin to notify test result                                                                                                                                     Jun 12, 2020    3 - Alpha              pytest
-   :pypi:`pytest-notimplemented`                    Pytest markers for not implemented features and tests.                                                                                                                    Aug 27, 2019    N/A                    pytest (>=5.1,<6.0)
-   :pypi:`pytest-notion`                            A PyTest Reporter to send test runs to Notion.so                                                                                                                          Aug 07, 2019    N/A                    N/A
-   :pypi:`pytest-nunit`                             A pytest plugin for generating NUnit3 test result XML output                                                                                                              Aug 04, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-ochrus`                            pytest results data-base and HTML reporter                                                                                                                                Feb 21, 2018    4 - Beta               N/A
-   :pypi:`pytest-odoo`                              py.test plugin to run Odoo tests                                                                                                                                          Nov 04, 2021    4 - Beta               pytest (>=2.9)
-   :pypi:`pytest-odoo-fixtures`                     Project description                                                                                                                                                       Jun 25, 2019    N/A                    N/A
-   :pypi:`pytest-oerp`                              pytest plugin to test OpenERP modules                                                                                                                                     Feb 28, 2012    3 - Alpha              N/A
-   :pypi:`pytest-ok`                                The ultimate pytest output plugin                                                                                                                                         Apr 01, 2019    4 - Beta               N/A
-   :pypi:`pytest-only`                              Use @pytest.mark.only to run a single test                                                                                                                                Jan 19, 2020    N/A                    N/A
-   :pypi:`pytest-oot`                               Run object-oriented tests in a simple format                                                                                                                              Sep 18, 2016    4 - Beta               N/A
-   :pypi:`pytest-openfiles`                         Pytest plugin for detecting inadvertent open file handles                                                                                                                 Apr 16, 2020    3 - Alpha              pytest (>=4.6)
-   :pypi:`pytest-opentmi`                           pytest plugin for publish results to opentmi                                                                                                                              Nov 04, 2021    5 - Production/Stable  pytest (>=5.0)
-   :pypi:`pytest-operator`                          Fixtures for Operators                                                                                                                                                    Oct 26, 2021    N/A                    N/A
-   :pypi:`pytest-optional`                          include/exclude values of fixtures in pytest                                                                                                                              Oct 07, 2015    N/A                    N/A
-   :pypi:`pytest-optional-tests`                    Easy declaration of optional tests (i.e., that are not run by default)                                                                                                    Jul 09, 2019    4 - Beta               pytest (>=4.5.0)
-   :pypi:`pytest-orchestration`                     A pytest plugin for orchestrating tests                                                                                                                                   Jul 18, 2019    N/A                    N/A
-   :pypi:`pytest-order`                             pytest plugin to run your tests in a specific order                                                                                                                       May 30, 2021    4 - Beta               pytest (>=5.0)
-   :pypi:`pytest-ordering`                          pytest plugin to run your tests in a specific order                                                                                                                       Nov 14, 2018    4 - Beta               pytest
-   :pypi:`pytest-osxnotify`                         OS X notifications for py.test results.                                                                                                                                   May 15, 2015    N/A                    N/A
-   :pypi:`pytest-otel`                              pytest-otel report OpenTelemetry traces about test executed                                                                                                               Dec 03, 2021    N/A                    N/A
-   :pypi:`pytest-pact`                              A simple plugin to use with pytest                                                                                                                                        Jan 07, 2019    4 - Beta               N/A
-   :pypi:`pytest-pahrametahrize`                    Parametrize your tests with a Boston accent.                                                                                                                              Nov 24, 2021    4 - Beta               pytest (>=6.0,<7.0)
-   :pypi:`pytest-parallel`                          a pytest plugin for parallel and concurrent testing                                                                                                                       Oct 10, 2021    3 - Alpha              pytest (>=3.0.0)
-   :pypi:`pytest-parallel-39`                       a pytest plugin for parallel and concurrent testing                                                                                                                       Jul 12, 2021    3 - Alpha              pytest (>=3.0.0)
-   :pypi:`pytest-param`                             pytest plugin to test all, first, last or random params                                                                                                                   Sep 11, 2016    4 - Beta               pytest (>=2.6.0)
-   :pypi:`pytest-paramark`                          Configure pytest fixtures using a combination of"parametrize" and markers                                                                                                 Jan 10, 2020    4 - Beta               pytest (>=4.5.0)
-   :pypi:`pytest-parametrization`                   Simpler PyTest parametrization                                                                                                                                            Nov 30, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-parametrize-cases`                 A more user-friendly way to write parametrized tests.                                                                                                                     Dec 12, 2020    N/A                    pytest (>=6.1.2,<7.0.0)
-   :pypi:`pytest-parametrized`                      Pytest plugin for parametrizing tests with default iterables.                                                                                                             Oct 19, 2020    5 - Production/Stable  pytest
-   :pypi:`pytest-parawtf`                           Finally spell paramete?ri[sz]e correctly                                                                                                                                  Dec 03, 2018    4 - Beta               pytest (>=3.6.0)
-   :pypi:`pytest-pass`                              Check out https://github.com/elilutsky/pytest-pass                                                                                                                        Dec 04, 2019    N/A                    N/A
-   :pypi:`pytest-passrunner`                        Pytest plugin providing the 'run_on_pass' marker                                                                                                                          Feb 10, 2021    5 - Production/Stable  pytest (>=4.6.0)
-   :pypi:`pytest-paste-config`                      Allow setting the path to a paste config file                                                                                                                             Sep 18, 2013    3 - Alpha              N/A
-   :pypi:`pytest-patches`                           A contextmanager pytest fixture for handling multiple mock patches                                                                                                        Aug 30, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-pdb`                               pytest plugin which adds pdb helper commands related to pytest.                                                                                                           Jul 31, 2018    N/A                    N/A
-   :pypi:`pytest-peach`                             pytest plugin for fuzzing with Peach API Security                                                                                                                         Apr 12, 2019    4 - Beta               pytest (>=2.8.7)
-   :pypi:`pytest-pep257`                            py.test plugin for pep257                                                                                                                                                 Jul 09, 2016    N/A                    N/A
-   :pypi:`pytest-pep8`                              pytest plugin to check PEP8 requirements                                                                                                                                  Apr 27, 2014    N/A                    N/A
-   :pypi:`pytest-percent`                           Change the exit code of pytest test sessions when a required percent of tests pass.                                                                                       May 21, 2020    N/A                    pytest (>=5.2.0)
-   :pypi:`pytest-perf`                              pytest-perf                                                                                                                                                               Jun 27, 2021    5 - Production/Stable  pytest (>=4.6) ; extra == 'testing'
-   :pypi:`pytest-performance`                       A simple plugin to ensure the execution of critical sections of code has not been impacted                                                                                Sep 11, 2020    5 - Production/Stable  pytest (>=3.7.0)
-   :pypi:`pytest-persistence`                       Pytest tool for persistent objects                                                                                                                                        Nov 06, 2021    N/A                    N/A
-   :pypi:`pytest-pgsql`                             Pytest plugins and helpers for tests using a Postgres database.                                                                                                           May 13, 2020    5 - Production/Stable  pytest (>=3.0.0)
-   :pypi:`pytest-phmdoctest`                        pytest plugin to test Python examples in Markdown using phmdoctest.                                                                                                       Nov 10, 2021    4 - Beta               pytest (>=6.2) ; extra == 'test'
-   :pypi:`pytest-picked`                            Run the tests related to the changed files                                                                                                                                Dec 23, 2020    N/A                    pytest (>=3.5.0)
-   :pypi:`pytest-pigeonhole`                                                                                                                                                                                                  Jun 25, 2018    5 - Production/Stable  pytest (>=3.4)
-   :pypi:`pytest-pikachu`                           Show surprise when tests are passing                                                                                                                                      Aug 05, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-pilot`                             Slice in your test base thanks to powerful markers.                                                                                                                       Oct 09, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-pings`                             🦊 The pytest plugin for Firefox Telemetry 📊                                                                                                                             Jun 29, 2019    3 - Alpha              pytest (>=5.0.0)
-   :pypi:`pytest-pinned`                            A simple pytest plugin for pinning tests                                                                                                                                  Sep 17, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-pinpoint`                          A pytest plugin which runs SBFL algorithms to detect faults.                                                                                                              Sep 25, 2020    N/A                    pytest (>=4.4.0)
-   :pypi:`pytest-pipeline`                          Pytest plugin for functional testing of data analysispipelines                                                                                                            Jan 24, 2017    3 - Alpha              N/A
-   :pypi:`pytest-platform-markers`                  Markers for pytest to skip tests on specific platforms                                                                                                                    Sep 09, 2019    4 - Beta               pytest (>=3.6.0)
-   :pypi:`pytest-play`                              pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files                                                         Jun 12, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-playbook`                          Pytest plugin for reading playbooks.                                                                                                                                      Jan 21, 2021    3 - Alpha              pytest (>=6.1.2,<7.0.0)
-   :pypi:`pytest-playwright`                        A pytest wrapper with fixtures for Playwright to automate web browsers                                                                                                    Oct 28, 2021    N/A                    pytest
-   :pypi:`pytest-playwrights`                       A pytest wrapper with fixtures for Playwright to automate web browsers                                                                                                    Dec 02, 2021    N/A                    N/A
-   :pypi:`pytest-playwright-snapshot`               A pytest wrapper for snapshot testing with playwright                                                                                                                     Aug 19, 2021    N/A                    N/A
-   :pypi:`pytest-plt`                               Fixtures for quickly making Matplotlib plots in tests                                                                                                                     Aug 17, 2020    5 - Production/Stable  pytest
-   :pypi:`pytest-plugin-helpers`                    A plugin to help developing and testing other plugins                                                                                                                     Nov 23, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-plus`                              PyTest Plus Plugin :: extends pytest functionality                                                                                                                        Mar 19, 2020    5 - Production/Stable  pytest (>=3.50)
-   :pypi:`pytest-pmisc`                                                                                                                                                                                                       Mar 21, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-pointers`                          Pytest plugin to define functions you test with special marks for better navigation and reports                                                                           Oct 14, 2021    N/A                    N/A
-   :pypi:`pytest-polarion-cfme`                     pytest plugin for collecting test cases and recording test results                                                                                                        Nov 13, 2017    3 - Alpha              N/A
-   :pypi:`pytest-polarion-collect`                  pytest plugin for collecting polarion test cases data                                                                                                                     Jun 18, 2020    3 - Alpha              pytest
-   :pypi:`pytest-polecat`                           Provides Polecat pytest fixtures                                                                                                                                          Aug 12, 2019    4 - Beta               N/A
-   :pypi:`pytest-ponyorm`                           PonyORM in Pytest                                                                                                                                                         Oct 31, 2018    N/A                    pytest (>=3.1.1)
-   :pypi:`pytest-poo`                               Visualize your crappy tests                                                                                                                                               Mar 25, 2021    5 - Production/Stable  pytest (>=2.3.4)
-   :pypi:`pytest-poo-fail`                          Visualize your failed tests with poo                                                                                                                                      Feb 12, 2015    5 - Production/Stable  N/A
-   :pypi:`pytest-pop`                               A pytest plugin to help with testing pop projects                                                                                                                         Aug 19, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-portion`                           Select a portion of the collected tests                                                                                                                                   Jan 28, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-postgres`                          Run PostgreSQL in Docker container in Pytest.                                                                                                                             Mar 22, 2020    N/A                    pytest
-   :pypi:`pytest-postgresql`                        Postgresql fixtures and fixture factories for Pytest.                                                                                                                     Nov 05, 2021    5 - Production/Stable  pytest (>=3.0.0)
-   :pypi:`pytest-power`                             pytest plugin with powerful fixtures                                                                                                                                      Dec 31, 2020    N/A                    pytest (>=5.4)
-   :pypi:`pytest-pretty-terminal`                   pytest plugin for generating prettier terminal output                                                                                                                     Nov 24, 2021    N/A                    pytest (>=3.4.1)
-   :pypi:`pytest-pride`                             Minitest-style test colors                                                                                                                                                Apr 02, 2016    3 - Alpha              N/A
-   :pypi:`pytest-print`                             pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout)                                               Jun 17, 2021    5 - Production/Stable  pytest (>=6)
-   :pypi:`pytest-profiling`                         Profiling plugin for py.test                                                                                                                                              May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-progress`                          pytest plugin for instant test progress status                                                                                                                            Nov 09, 2021    5 - Production/Stable  pytest (>=2.7)
-   :pypi:`pytest-prometheus`                        Report test pass / failures to a Prometheus PushGateway                                                                                                                   Oct 03, 2017    N/A                    N/A
-   :pypi:`pytest-prosper`                           Test helpers for Prosper projects                                                                                                                                         Sep 24, 2018    N/A                    N/A
-   :pypi:`pytest-pspec`                             A rspec format reporter for Python ptest                                                                                                                                  Jun 02, 2020    4 - Beta               pytest (>=3.0.0)
-   :pypi:`pytest-psqlgraph`                         pytest plugin for testing applications that use psqlgraph                                                                                                                 Oct 19, 2021    4 - Beta               pytest (>=6.0)
-   :pypi:`pytest-ptera`                             Use ptera probes in tests                                                                                                                                                 Oct 20, 2021    N/A                    pytest (>=6.2.4,<7.0.0)
-   :pypi:`pytest-pudb`                              Pytest PuDB debugger integration                                                                                                                                          Oct 25, 2018    3 - Alpha              pytest (>=2.0)
-   :pypi:`pytest-purkinje`                          py.test plugin for purkinje test runner                                                                                                                                   Oct 28, 2017    2 - Pre-Alpha          N/A
-   :pypi:`pytest-pycharm`                           Plugin for py.test to enter PyCharm debugger on uncaught exceptions                                                                                                       Aug 13, 2020    5 - Production/Stable  pytest (>=2.3)
-   :pypi:`pytest-pycodestyle`                       pytest plugin to run pycodestyle                                                                                                                                          Aug 10, 2020    3 - Alpha              N/A
-   :pypi:`pytest-pydev`                             py.test plugin to connect to a remote debug server with PyDev or PyCharm.                                                                                                 Nov 15, 2017    3 - Alpha              N/A
-   :pypi:`pytest-pydocstyle`                        pytest plugin to run pydocstyle                                                                                                                                           Aug 10, 2020    3 - Alpha              N/A
-   :pypi:`pytest-pylint`                            pytest plugin to check source code with pylint                                                                                                                            Nov 09, 2020    5 - Production/Stable  pytest (>=5.4)
-   :pypi:`pytest-pypi`                              Easily test your HTTP library against a local copy of pypi                                                                                                                Mar 04, 2018    3 - Alpha              N/A
-   :pypi:`pytest-pypom-navigation`                  Core engine for cookiecutter-qa and pytest-play packages                                                                                                                  Feb 18, 2019    4 - Beta               pytest (>=3.0.7)
-   :pypi:`pytest-pyppeteer`                         A plugin to run pyppeteer in pytest.                                                                                                                                      Feb 16, 2021    4 - Beta               pytest (>=6.0.2)
-   :pypi:`pytest-pyq`                               Pytest fixture "q" for pyq                                                                                                                                                Mar 10, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-pyramid`                           pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite                                                                                Oct 15, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-pyramid-server`                    Pyramid server fixture for py.test                                                                                                                                        May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-pyright`                           Pytest plugin for type checking code with Pyright                                                                                                                         Aug 16, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-pytestrail`                        Pytest plugin for interaction with TestRail                                                                                                                               Aug 27, 2020    4 - Beta               pytest (>=3.8.0)
-   :pypi:`pytest-pythonpath`                        pytest plugin for adding to the PYTHONPATH from command line or configs.                                                                                                  Aug 22, 2018    5 - Production/Stable  N/A
-   :pypi:`pytest-pytorch`                           pytest plugin for a better developer experience when working with the PyTorch test suite                                                                                  May 25, 2021    4 - Beta               pytest
-   :pypi:`pytest-qasync`                            Pytest support for qasync.                                                                                                                                                Jul 12, 2021    4 - Beta               pytest (>=5.4.0)
-   :pypi:`pytest-qatouch`                           Pytest plugin for uploading test results to your QA Touch Testrun.                                                                                                        Jun 26, 2021    4 - Beta               pytest (>=6.2.0)
-   :pypi:`pytest-qgis`                              A pytest plugin for testing QGIS python plugins                                                                                                                           Nov 25, 2021    5 - Production/Stable  pytest (>=6.2.3)
-   :pypi:`pytest-qml`                               Run QML Tests with pytest                                                                                                                                                 Dec 02, 2020    4 - Beta               pytest (>=6.0.0)
-   :pypi:`pytest-qr`                                pytest plugin to generate test result QR codes                                                                                                                            Nov 25, 2021    4 - Beta               N/A
-   :pypi:`pytest-qt`                                pytest support for PyQt and PySide applications                                                                                                                           Jun 13, 2021    5 - Production/Stable  pytest (>=3.0.0)
-   :pypi:`pytest-qt-app`                            QT app fixture for py.test                                                                                                                                                Dec 23, 2015    5 - Production/Stable  N/A
-   :pypi:`pytest-quarantine`                        A plugin for pytest to manage expected test failures                                                                                                                      Nov 24, 2019    5 - Production/Stable  pytest (>=4.6)
-   :pypi:`pytest-quickcheck`                        pytest plugin to generate random data inspired by QuickCheck                                                                                                              Nov 15, 2020    4 - Beta               pytest (<6.0.0,>=4.0)
-   :pypi:`pytest-rabbitmq`                          RabbitMQ process and client fixtures for pytest                                                                                                                           Jun 02, 2021    5 - Production/Stable  pytest (>=3.0.0)
-   :pypi:`pytest-race`                              Race conditions tester for pytest                                                                                                                                         Nov 21, 2016    4 - Beta               N/A
-   :pypi:`pytest-rage`                              pytest plugin to implement PEP712                                                                                                                                         Oct 21, 2011    3 - Alpha              N/A
-   :pypi:`pytest-railflow-testrail-reporter`        Generate json reports along with specified metadata defined in test markers.                                                                                              Dec 02, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-raises`                            An implementation of pytest.raises as a pytest.mark fixture                                                                                                               Apr 23, 2020    N/A                    pytest (>=3.2.2)
-   :pypi:`pytest-raisesregexp`                      Simple pytest plugin to look for regex in Exceptions                                                                                                                      Dec 18, 2015    N/A                    N/A
-   :pypi:`pytest-raisin`                            Plugin enabling the use of exception instances with pytest.raises                                                                                                         Jun 25, 2020    N/A                    pytest
-   :pypi:`pytest-random`                            py.test plugin to randomize tests                                                                                                                                         Apr 28, 2013    3 - Alpha              N/A
-   :pypi:`pytest-randomly`                          Pytest plugin to randomly order tests and control random.seed.                                                                                                            Nov 30, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-randomness`                        Pytest plugin about random seed management                                                                                                                                May 30, 2019    3 - Alpha              N/A
-   :pypi:`pytest-random-num`                        Randomise the order in which pytest tests are run with some control over the randomness                                                                                   Oct 19, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-random-order`                      Randomise the order in which pytest tests are run with some control over the randomness                                                                                   Nov 30, 2018    5 - Production/Stable  pytest (>=3.0.0)
-   :pypi:`pytest-readme`                            Test your README.md file                                                                                                                                                  Dec 28, 2014    5 - Production/Stable  N/A
-   :pypi:`pytest-reana`                             Pytest fixtures for REANA.                                                                                                                                                Nov 22, 2021    3 - Alpha              N/A
-   :pypi:`pytest-recording`                         A pytest plugin that allows you recording of network interactions via VCR.py                                                                                              Jul 08, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-recordings`                        Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal                                                                     Aug 13, 2020    N/A                    N/A
-   :pypi:`pytest-redis`                             Redis fixtures and fixture factories for Pytest.                                                                                                                          Nov 03, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-redislite`                         Pytest plugin for testing code using Redis                                                                                                                                Sep 19, 2021    4 - Beta               pytest
-   :pypi:`pytest-redmine`                           Pytest plugin for redmine                                                                                                                                                 Mar 19, 2018    1 - Planning           N/A
-   :pypi:`pytest-ref`                               A plugin to store reference files to ease regression testing                                                                                                              Nov 23, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-reference-formatter`               Conveniently run pytest with a dot-formatted test reference.                                                                                                              Oct 01, 2019    4 - Beta               N/A
-   :pypi:`pytest-regressions`                       Easy to use fixtures to write regression tests.                                                                                                                           Jan 27, 2021    5 - Production/Stable  pytest (>=3.5.0)
-   :pypi:`pytest-regtest`                           pytest plugin for regression tests                                                                                                                                        Jun 03, 2021    N/A                    N/A
-   :pypi:`pytest-relative-order`                    a pytest plugin that sorts tests using "before" and "after" markers                                                                                                       May 17, 2021    4 - Beta               N/A
-   :pypi:`pytest-relaxed`                           Relaxed test discovery/organization for pytest                                                                                                                            Jun 14, 2019    5 - Production/Stable  pytest (<5,>=3)
-   :pypi:`pytest-remfiles`                          Pytest plugin to create a temporary directory with remote files                                                                                                           Jul 01, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-remotedata`                        Pytest plugin for controlling remote data access.                                                                                                                         Jul 20, 2019    3 - Alpha              pytest (>=3.1)
-   :pypi:`pytest-remote-response`                   Pytest plugin for capturing and mocking connection requests.                                                                                                              Jun 30, 2021    4 - Beta               pytest (>=4.6)
-   :pypi:`pytest-remove-stale-bytecode`             py.test plugin to remove stale byte code files.                                                                                                                           Mar 04, 2020    4 - Beta               pytest
-   :pypi:`pytest-reorder`                           Reorder tests depending on their paths and names.                                                                                                                         May 31, 2018    4 - Beta               pytest
-   :pypi:`pytest-repeat`                            pytest plugin for repeating tests                                                                                                                                         Oct 31, 2020    5 - Production/Stable  pytest (>=3.6)
-   :pypi:`pytest-replay`                            Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests                                                                    Jun 09, 2021    4 - Beta               pytest (>=3.0.0)
-   :pypi:`pytest-repo-health`                       A pytest plugin to report on repository standards conformance                                                                                                             Nov 23, 2021    3 - Alpha              pytest
-   :pypi:`pytest-report`                            Creates json report that is compatible with atom.io's linter message format                                                                                               May 11, 2016    4 - Beta               N/A
-   :pypi:`pytest-reporter`                          Generate Pytest reports with templates                                                                                                                                    Jul 22, 2021    4 - Beta               pytest
-   :pypi:`pytest-reporter-html1`                    A basic HTML report template for Pytest                                                                                                                                   Jun 08, 2021    4 - Beta               N/A
-   :pypi:`pytest-reportinfra`                       Pytest plugin for reportinfra                                                                                                                                             Aug 11, 2019    3 - Alpha              N/A
-   :pypi:`pytest-reporting`                         A plugin to report summarized results in a table format                                                                                                                   Oct 25, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-reportlog`                         Replacement for the --resultlog option, focused in simplicity and extensibility                                                                                           Dec 11, 2020    3 - Alpha              pytest (>=5.2)
-   :pypi:`pytest-report-me`                         A pytest plugin to generate report.                                                                                                                                       Dec 31, 2020    N/A                    pytest
-   :pypi:`pytest-report-parameters`                 pytest plugin for adding tests' parameters to junit report                                                                                                                Jun 18, 2020    3 - Alpha              pytest (>=2.4.2)
-   :pypi:`pytest-reportportal`                      Agent for Reporting results of tests to the Report Portal                                                                                                                 Jun 18, 2021    N/A                    pytest (>=3.8.0)
-   :pypi:`pytest-reqs`                              pytest plugin to check pinned requirements                                                                                                                                May 12, 2019    N/A                    pytest (>=2.4.2)
-   :pypi:`pytest-requests`                          A simple plugin to use with pytest                                                                                                                                        Jun 24, 2019    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-reraise`                           Make multi-threaded pytest test cases fail when they should                                                                                                               Jun 17, 2021    5 - Production/Stable  pytest (>=4.6)
-   :pypi:`pytest-rerun`                             Re-run only changed files in specified branch                                                                                                                             Jul 08, 2019    N/A                    pytest (>=3.6)
-   :pypi:`pytest-rerunfailures`                     pytest plugin to re-run tests to eliminate flaky failures                                                                                                                 Sep 17, 2021    5 - Production/Stable  pytest (>=5.3)
-   :pypi:`pytest-resilient-circuits`                Resilient Circuits fixtures for PyTest.                                                                                                                                   Nov 15, 2021    N/A                    N/A
-   :pypi:`pytest-resource`                          Load resource fixture plugin to use with pytest                                                                                                                           Nov 14, 2018    4 - Beta               N/A
-   :pypi:`pytest-resource-path`                     Provides path for uniform access to test resources in isolated directory                                                                                                  May 01, 2021    5 - Production/Stable  pytest (>=3.5.0)
-   :pypi:`pytest-responsemock`                      Simplified requests calls mocking for pytest                                                                                                                              Oct 10, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-responses`                         py.test integration for responses                                                                                                                                         Apr 26, 2021    N/A                    pytest (>=2.5)
-   :pypi:`pytest-restrict`                          Pytest plugin to restrict the test types allowed                                                                                                                          Aug 12, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-rethinkdb`                         A RethinkDB plugin for pytest.                                                                                                                                            Jul 24, 2016    4 - Beta               N/A
-   :pypi:`pytest-reverse`                           Pytest plugin to reverse test order.                                                                                                                                      Aug 12, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-ringo`                             pytest plugin to test webapplications using the Ringo webframework                                                                                                        Sep 27, 2017    3 - Alpha              N/A
-   :pypi:`pytest-rng`                               Fixtures for seeding tests and making randomness reproducible                                                                                                             Aug 08, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-roast`                             pytest plugin for ROAST configuration override and fixtures                                                                                                               Jul 29, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-rocketchat`                        Pytest to Rocket.Chat reporting plugin                                                                                                                                    Apr 18, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-rotest`                            Pytest integration with rotest                                                                                                                                            Sep 08, 2019    N/A                    pytest (>=3.5.0)
-   :pypi:`pytest-rpc`                               Extend py.test for RPC OpenStack testing.                                                                                                                                 Feb 22, 2019    4 - Beta               pytest (~=3.6)
-   :pypi:`pytest-rst`                               Test code from RST documents with pytest                                                                                                                                  Sep 21, 2021    N/A                    pytest
-   :pypi:`pytest-rt`                                pytest data collector plugin for Testgr                                                                                                                                   Sep 04, 2021    N/A                    N/A
-   :pypi:`pytest-rts`                               Coverage-based regression test selection (RTS) plugin for pytest                                                                                                          May 17, 2021    N/A                    pytest
-   :pypi:`pytest-run-changed`                       Pytest plugin that runs changed tests only                                                                                                                                Apr 02, 2021    3 - Alpha              pytest
-   :pypi:`pytest-runfailed`                         implement a --failed option for pytest                                                                                                                                    Mar 24, 2016    N/A                    N/A
-   :pypi:`pytest-runner`                            Invoke py.test as distutils command with dependency resolution                                                                                                            May 19, 2021    5 - Production/Stable  pytest (>=4.6) ; extra == 'testing'
-   :pypi:`pytest-runtime-xfail`                     Call runtime_xfail() to mark running test as xfail.                                                                                                                       Aug 26, 2021    N/A                    N/A
-   :pypi:`pytest-salt`                              Pytest Salt Plugin                                                                                                                                                        Jan 27, 2020    4 - Beta               N/A
-   :pypi:`pytest-salt-containers`                   A Pytest plugin that builds and creates docker containers                                                                                                                 Nov 09, 2016    4 - Beta               N/A
-   :pypi:`pytest-salt-factories`                    Pytest Salt Plugin                                                                                                                                                        Sep 16, 2021    4 - Beta               pytest (>=6.0.0)
-   :pypi:`pytest-salt-from-filenames`               Simple PyTest Plugin For Salt's Test Suite Specifically                                                                                                                   Jan 29, 2019    4 - Beta               pytest (>=4.1)
-   :pypi:`pytest-salt-runtests-bridge`              Simple PyTest Plugin For Salt's Test Suite Specifically                                                                                                                   Dec 05, 2019    4 - Beta               pytest (>=4.1)
-   :pypi:`pytest-sanic`                             a pytest plugin for Sanic                                                                                                                                                 Oct 25, 2021    N/A                    pytest (>=5.2)
-   :pypi:`pytest-sanity`                                                                                                                                                                                                      Dec 07, 2020    N/A                    N/A
-   :pypi:`pytest-sa-pg`                                                                                                                                                                                                       May 14, 2019    N/A                    N/A
-   :pypi:`pytest-sbase`                             A complete web automation framework for end-to-end testing.                                                                                                               Dec 03, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-scenario`                          pytest plugin for test scenarios                                                                                                                                          Feb 06, 2017    3 - Alpha              N/A
-   :pypi:`pytest-schema`                            👍 Validate return values against a schema-like object in testing                                                                                                         Aug 31, 2020    5 - Production/Stable  pytest (>=3.5.0)
-   :pypi:`pytest-securestore`                       An encrypted password store for use within pytest cases                                                                                                                   Nov 08, 2021    4 - Beta               N/A
-   :pypi:`pytest-select`                            A pytest plugin which allows to (de-)select tests from a file.                                                                                                            Jan 18, 2019    3 - Alpha              pytest (>=3.0)
-   :pypi:`pytest-selenium`                          pytest plugin for Selenium                                                                                                                                                Sep 19, 2020    5 - Production/Stable  pytest (>=5.0.0)
-   :pypi:`pytest-seleniumbase`                      A complete web automation framework for end-to-end testing.                                                                                                               Dec 03, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-selenium-enhancer`                 pytest plugin for Selenium                                                                                                                                                Nov 26, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-selenium-pdiff`                    A pytest package implementing perceptualdiff for Selenium tests.                                                                                                          Apr 06, 2017    2 - Pre-Alpha          N/A
-   :pypi:`pytest-send-email`                        Send pytest execution result email                                                                                                                                        Dec 04, 2019    N/A                    N/A
-   :pypi:`pytest-sentry`                            A pytest plugin to send testrun information to Sentry.io                                                                                                                  Apr 21, 2021    N/A                    pytest
-   :pypi:`pytest-server-fixtures`                   Extensible server fixures for py.test                                                                                                                                     May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-serverless`                        Automatically mocks resources from serverless.yml in pytest using moto.                                                                                                   Nov 27, 2021    4 - Beta               N/A
-   :pypi:`pytest-services`                          Services plugin for pytest testing framework                                                                                                                              Oct 30, 2020    6 - Mature             N/A
-   :pypi:`pytest-session2file`                      pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test.                         Jan 26, 2021    3 - Alpha              pytest
-   :pypi:`pytest-session-fixture-globalize`         py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules                                                           May 15, 2018    4 - Beta               N/A
-   :pypi:`pytest-session_to_file`                   pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test.                                                                        Oct 01, 2015    3 - Alpha              N/A
-   :pypi:`pytest-sftpserver`                        py.test plugin to locally test sftp server connections.                                                                                                                   Sep 16, 2019    4 - Beta               N/A
-   :pypi:`pytest-shard`                                                                                                                                                                                                       Dec 11, 2020    4 - Beta               pytest
-   :pypi:`pytest-shell`                             A pytest plugin to help with testing shell scripts / black box commands                                                                                                   Nov 07, 2021    N/A                    N/A
-   :pypi:`pytest-sheraf`                            Versatile ZODB abstraction layer - pytest fixtures                                                                                                                        Feb 11, 2020    N/A                    pytest
-   :pypi:`pytest-sherlock`                          pytest plugin help to find coupled tests                                                                                                                                  Nov 18, 2021    5 - Production/Stable  pytest (>=3.5.1)
-   :pypi:`pytest-shortcuts`                         Expand command-line shortcuts listed in pytest configuration                                                                                                              Oct 29, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-shutil`                            A goodie-bag of unix shell and environment tools for py.test                                                                                                              May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-simplehttpserver`                  Simple pytest fixture to spin up an HTTP server                                                                                                                           Jun 24, 2021    4 - Beta               N/A
-   :pypi:`pytest-simple-plugin`                     Simple pytest plugin                                                                                                                                                      Nov 27, 2019    N/A                    N/A
-   :pypi:`pytest-simple-settings`                   simple-settings plugin for pytest                                                                                                                                         Nov 17, 2020    4 - Beta               pytest
-   :pypi:`pytest-single-file-logging`               Allow for multiple processes to log to a single file                                                                                                                      May 05, 2016    4 - Beta               pytest (>=2.8.1)
-   :pypi:`pytest-skip-markers`                      Pytest Salt Plugin                                                                                                                                                        Oct 04, 2021    4 - Beta               pytest (>=6.0.0)
-   :pypi:`pytest-skipper`                           A plugin that selects only tests with changes in execution path                                                                                                           Mar 26, 2017    3 - Alpha              pytest (>=3.0.6)
-   :pypi:`pytest-skippy`                            Automatically skip tests that don't need to run!                                                                                                                          Jan 27, 2018    3 - Alpha              pytest (>=2.3.4)
-   :pypi:`pytest-skip-slow`                         A pytest plugin to skip \`@pytest.mark.slow\` tests by default.                                                                                                           Sep 28, 2021    N/A                    N/A
-   :pypi:`pytest-slack`                             Pytest to Slack reporting plugin                                                                                                                                          Dec 15, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-slow`                              A pytest plugin to skip \`@pytest.mark.slow\` tests by default.                                                                                                           Sep 28, 2021    N/A                    N/A
-   :pypi:`pytest-smartcollect`                      A plugin for collecting tests that touch changed code                                                                                                                     Oct 04, 2018    N/A                    pytest (>=3.5.0)
-   :pypi:`pytest-smartcov`                          Smart coverage plugin for pytest.                                                                                                                                         Sep 30, 2017    3 - Alpha              N/A
-   :pypi:`pytest-smtp`                              Send email with pytest execution result                                                                                                                                   Feb 20, 2021    N/A                    pytest
-   :pypi:`pytest-snail`                             Plugin for adding a marker to slow running tests. 🐌                                                                                                                      Nov 04, 2019    3 - Alpha              pytest (>=5.0.1)
-   :pypi:`pytest-snapci`                            py.test plugin for Snap-CI                                                                                                                                                Nov 12, 2015    N/A                    N/A
-   :pypi:`pytest-snapshot`                          A plugin for snapshot testing with pytest.                                                                                                                                Dec 02, 2021    4 - Beta               pytest (>=3.0.0)
-   :pypi:`pytest-snmpserver`                                                                                                                                                                                                  May 12, 2021    N/A                    N/A
-   :pypi:`pytest-socket`                            Pytest Plugin to disable socket calls during tests                                                                                                                        Aug 28, 2021    4 - Beta               pytest (>=3.6.3)
-   :pypi:`pytest-soft-assertions`                                                                                                                                                                                             May 05, 2020    3 - Alpha              pytest
-   :pypi:`pytest-solr`                              Solr process and client fixtures for py.test.                                                                                                                             May 11, 2020    3 - Alpha              pytest (>=3.0.0)
-   :pypi:`pytest-sorter`                            A simple plugin to first execute tests that historically failed more                                                                                                      Apr 20, 2021    4 - Beta               pytest (>=3.1.1)
-   :pypi:`pytest-sourceorder`                       Test-ordering plugin for pytest                                                                                                                                           Sep 01, 2021    4 - Beta               pytest
-   :pypi:`pytest-spark`                             pytest plugin to run the tests with support of pyspark.                                                                                                                   Feb 23, 2020    4 - Beta               pytest
-   :pypi:`pytest-spawner`                           py.test plugin to spawn process and communicate with them.                                                                                                                Jul 31, 2015    4 - Beta               N/A
-   :pypi:`pytest-spec`                              Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION.                                                                             May 04, 2021    N/A                    N/A
-   :pypi:`pytest-sphinx`                            Doctest plugin for pytest with support for Sphinx-specific doctest-directives                                                                                             Aug 05, 2020    4 - Beta               N/A
-   :pypi:`pytest-spiratest`                         Exports unit tests as test runs in SpiraTest/Team/Plan                                                                                                                    Oct 13, 2021    N/A                    N/A
-   :pypi:`pytest-splinter`                          Splinter plugin for pytest testing framework                                                                                                                              Dec 25, 2020    6 - Mature             N/A
-   :pypi:`pytest-split`                             Pytest plugin which splits the test suite to equally sized sub suites based on test execution time.                                                                       Nov 09, 2021    4 - Beta               pytest (>=5,<7)
-   :pypi:`pytest-splitio`                           Split.io SDK integration for e2e tests                                                                                                                                    Sep 22, 2020    N/A                    pytest (<7,>=5.0)
-   :pypi:`pytest-split-tests`                       A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups.             Jul 30, 2021    5 - Production/Stable  pytest (>=2.5)
-   :pypi:`pytest-split-tests-tresorit`                                                                                                                                                                                        Feb 22, 2021    1 - Planning           N/A
-   :pypi:`pytest-splunk-addon`                      A Dynamic test tool for Splunk Apps and Add-ons                                                                                                                           Nov 29, 2021    N/A                    pytest (>5.4.0,<6.3)
-   :pypi:`pytest-splunk-addon-ui-smartx`            Library to support testing Splunk Add-on UX                                                                                                                               Oct 07, 2021    N/A                    N/A
-   :pypi:`pytest-splunk-env`                        pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud                                                                                                   Oct 22, 2020    N/A                    pytest (>=6.1.1,<7.0.0)
-   :pypi:`pytest-sqitch`                            sqitch for pytest                                                                                                                                                         Apr 06, 2020    4 - Beta               N/A
-   :pypi:`pytest-sqlalchemy`                        pytest plugin with sqlalchemy related fixtures                                                                                                                            Mar 13, 2018    3 - Alpha              N/A
-   :pypi:`pytest-sql-bigquery`                      Yet another SQL-testing framework for BigQuery provided by pytest plugin                                                                                                  Dec 19, 2019    N/A                    pytest
-   :pypi:`pytest-srcpaths`                          Add paths to sys.path                                                                                                                                                     Oct 15, 2021    N/A                    N/A
-   :pypi:`pytest-ssh`                               pytest plugin for ssh command run                                                                                                                                         May 27, 2019    N/A                    pytest
-   :pypi:`pytest-start-from`                        Start pytest run from a given point                                                                                                                                       Apr 11, 2016    N/A                    N/A
-   :pypi:`pytest-statsd`                            pytest plugin for reporting to graphite                                                                                                                                   Nov 30, 2018    5 - Production/Stable  pytest (>=3.0.0)
-   :pypi:`pytest-stepfunctions`                     A small description                                                                                                                                                       May 08, 2021    4 - Beta               pytest
-   :pypi:`pytest-steps`                             Create step-wise / incremental tests in pytest.                                                                                                                           Sep 23, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-stepwise`                          Run a test suite one failing test at a time.                                                                                                                              Dec 01, 2015    4 - Beta               N/A
-   :pypi:`pytest-stoq`                              A plugin to pytest stoq                                                                                                                                                   Feb 09, 2021    4 - Beta               N/A
-   :pypi:`pytest-stress`                            A Pytest plugin that allows you to loop tests for a user defined amount of time.                                                                                          Dec 07, 2019    4 - Beta               pytest (>=3.6.0)
-   :pypi:`pytest-structlog`                         Structured logging assertions                                                                                                                                             Sep 21, 2021    N/A                    pytest
-   :pypi:`pytest-structmpd`                         provide structured temporary directory                                                                                                                                    Oct 17, 2018    N/A                    N/A
-   :pypi:`pytest-stub`                              Stub packages, modules and attributes.                                                                                                                                    Apr 28, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-stubprocess`                       Provide stub implementations for subprocesses in Python tests                                                                                                             Sep 17, 2018    3 - Alpha              pytest (>=3.5.0)
-   :pypi:`pytest-study`                             A pytest plugin to organize long run tests (named studies) without interfering the regular tests                                                                          Sep 26, 2017    3 - Alpha              pytest (>=2.0)
-   :pypi:`pytest-subprocess`                        A plugin to fake subprocess for pytest                                                                                                                                    Nov 07, 2021    5 - Production/Stable  pytest (>=4.0.0)
-   :pypi:`pytest-subtesthack`                       A hack to explicitly set up and tear down fixtures.                                                                                                                       Mar 02, 2021    N/A                    N/A
-   :pypi:`pytest-subtests`                          unittest subTest() support and subtests fixture                                                                                                                           May 29, 2021    4 - Beta               pytest (>=5.3.0)
-   :pypi:`pytest-subunit`                           pytest-subunit is a plugin for py.test which outputs testsresult in subunit format.                                                                                       Aug 29, 2017    N/A                    N/A
-   :pypi:`pytest-sugar`                             pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly).                                  Jul 06, 2020    3 - Alpha              N/A
-   :pypi:`pytest-sugar-bugfix159`                   Workaround for https://github.com/Frozenball/pytest-sugar/issues/159                                                                                                      Nov 07, 2018    5 - Production/Stable  pytest (!=3.7.3,>=3.5); extra == 'testing'
-   :pypi:`pytest-super-check`                       Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc.                                                                                          Aug 12, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-svn`                               SVN repository fixture for py.test                                                                                                                                        May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-symbols`                           pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests.                                                               Nov 20, 2017    3 - Alpha              N/A
-   :pypi:`pytest-takeltest`                         Fixtures for ansible, testinfra and molecule                                                                                                                              Oct 13, 2021    N/A                    N/A
-   :pypi:`pytest-talisker`                                                                                                                                                                                                    Nov 28, 2021    N/A                    N/A
-   :pypi:`pytest-tap`                               Test Anything Protocol (TAP) reporting plugin for pytest                                                                                                                  Oct 27, 2021    5 - Production/Stable  pytest (>=3.0)
-   :pypi:`pytest-tape`                              easy assertion with expected results saved to yaml files                                                                                                                  Mar 17, 2021    4 - Beta               N/A
-   :pypi:`pytest-target`                            Pytest plugin for remote target orchestration.                                                                                                                            Jan 21, 2021    3 - Alpha              pytest (>=6.1.2,<7.0.0)
-   :pypi:`pytest-tblineinfo`                        tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used                                                          Dec 01, 2015    3 - Alpha              pytest (>=2.0)
-   :pypi:`pytest-teamcity-logblock`                 py.test plugin to introduce block structure in teamcity build log, if output is not captured                                                                              May 15, 2018    4 - Beta               N/A
-   :pypi:`pytest-telegram`                          Pytest to Telegram reporting plugin                                                                                                                                       Dec 10, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-tempdir`                           Predictable and repeatable tempdir support.                                                                                                                               Oct 11, 2019    4 - Beta               pytest (>=2.8.1)
-   :pypi:`pytest-terraform`                         A pytest plugin for using terraform fixtures                                                                                                                              Nov 10, 2021    N/A                    pytest (>=6.0)
-   :pypi:`pytest-terraform-fixture`                 generate terraform resources to use with pytest                                                                                                                           Nov 14, 2018    4 - Beta               N/A
-   :pypi:`pytest-testbook`                          A plugin to run tests written in Jupyter notebook                                                                                                                         Dec 11, 2016    3 - Alpha              N/A
-   :pypi:`pytest-testconfig`                        Test configuration plugin for pytest.                                                                                                                                     Jan 11, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-testdirectory`                     A py.test plugin providing temporary directories in unit tests.                                                                                                           Nov 06, 2018    5 - Production/Stable  pytest
-   :pypi:`pytest-testdox`                           A testdox format reporter for pytest                                                                                                                                      Oct 13, 2020    5 - Production/Stable  pytest (>=3.7.0)
-   :pypi:`pytest-test-groups`                       A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups.                                                                          Oct 25, 2016    5 - Production/Stable  N/A
-   :pypi:`pytest-testinfra`                         Test infrastructures                                                                                                                                                      Jun 20, 2021    5 - Production/Stable  pytest (!=3.0.2)
-   :pypi:`pytest-testlink-adaptor`                  pytest reporting plugin for testlink                                                                                                                                      Dec 20, 2018    4 - Beta               pytest (>=2.6)
-   :pypi:`pytest-testmon`                           selects tests affected by changed files and methods                                                                                                                       Oct 22, 2021    4 - Beta               N/A
-   :pypi:`pytest-testobject`                        Plugin to use TestObject Suites with Pytest                                                                                                                               Sep 24, 2019    4 - Beta               pytest (>=3.1.1)
-   :pypi:`pytest-testrail`                          pytest plugin for creating TestRail runs and adding results                                                                                                               Aug 27, 2020    N/A                    pytest (>=3.6)
-   :pypi:`pytest-testrail2`                         A small example package                                                                                                                                                   Nov 17, 2020    N/A                    pytest (>=5)
-   :pypi:`pytest-testrail-api`                      Плагин Pytest, для интеграции с TestRail                                                                                                                                  Nov 30, 2021    N/A                    pytest (>=5.5)
-   :pypi:`pytest-testrail-api-client`               TestRail Api Python Client                                                                                                                                                Dec 03, 2021    N/A                    pytest
-   :pypi:`pytest-testrail-appetize`                 pytest plugin for creating TestRail runs and adding results                                                                                                               Sep 29, 2021    N/A                    N/A
-   :pypi:`pytest-testrail-client`                   pytest plugin for Testrail                                                                                                                                                Sep 29, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-testrail-e2e`                      pytest plugin for creating TestRail runs and adding results                                                                                                               Oct 11, 2021    N/A                    pytest (>=3.6)
-   :pypi:`pytest-testrail-ns`                       pytest plugin for creating TestRail runs and adding results                                                                                                               Oct 08, 2021    N/A                    pytest (>=3.6)
-   :pypi:`pytest-testrail-plugin`                   PyTest plugin for TestRail                                                                                                                                                Apr 21, 2020    3 - Alpha              pytest
-   :pypi:`pytest-testrail-reporter`                                                                                                                                                                                           Sep 10, 2018    N/A                    N/A
-   :pypi:`pytest-testreport`                                                                                                                                                                                                  Nov 12, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-testslide`                         TestSlide fixture for pytest                                                                                                                                              Jan 07, 2021    5 - Production/Stable  pytest (~=6.2)
-   :pypi:`pytest-test-this`                         Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply                                               Sep 15, 2019    2 - Pre-Alpha          pytest (>=2.3)
-   :pypi:`pytest-test-utils`                                                                                                                                                                                                  Nov 30, 2021    N/A                    pytest (>=5)
-   :pypi:`pytest-tesults`                           Tesults plugin for pytest                                                                                                                                                 Jul 31, 2021    5 - Production/Stable  pytest (>=3.5.0)
-   :pypi:`pytest-tezos`                             pytest-ligo                                                                                                                                                               Jan 16, 2020    4 - Beta               N/A
-   :pypi:`pytest-thawgun`                           Pytest plugin for time travel                                                                                                                                             May 26, 2020    3 - Alpha              N/A
-   :pypi:`pytest-threadleak`                        Detects thread leaks                                                                                                                                                      Sep 08, 2017    4 - Beta               N/A
-   :pypi:`pytest-tick`                              Ticking on tests                                                                                                                                                          Aug 31, 2021    5 - Production/Stable  pytest (>=6.2.5,<7.0.0)
-   :pypi:`pytest-timeit`                            A pytest plugin to time test function runs                                                                                                                                Oct 13, 2016    4 - Beta               N/A
-   :pypi:`pytest-timeout`                           pytest plugin to abort hanging tests                                                                                                                                      Oct 11, 2021    5 - Production/Stable  pytest (>=5.0.0)
-   :pypi:`pytest-timeouts`                          Linux-only Pytest plugin to control durations of various test case execution phases                                                                                       Sep 21, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-timer`                             A timer plugin for pytest                                                                                                                                                 Jun 02, 2021    N/A                    N/A
-   :pypi:`pytest-timestamper`                       Pytest plugin to add a timestamp prefix to the pytest output                                                                                                              Jun 06, 2021    N/A                    N/A
-   :pypi:`pytest-tipsi-django`                                                                                                                                                                                                Nov 17, 2021    4 - Beta               pytest (>=6.0.0)
-   :pypi:`pytest-tipsi-testing`                     Better fixtures management. Various helpers                                                                                                                               Nov 04, 2020    4 - Beta               pytest (>=3.3.0)
-   :pypi:`pytest-tldr`                              A pytest plugin that limits the output to just the things you need.                                                                                                       Mar 12, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-tm4j-reporter`                     Cloud Jira Test Management (TM4J) PyTest reporter plugin                                                                                                                  Sep 01, 2020    N/A                    pytest
-   :pypi:`pytest-tmreport`                          this is a vue-element ui report for pytest                                                                                                                                Nov 17, 2021    N/A                    N/A
-   :pypi:`pytest-todo`                              A small plugin for the pytest testing framework, marking TODO comments as failure                                                                                         May 23, 2019    4 - Beta               pytest
-   :pypi:`pytest-tomato`                                                                                                                                                                                                      Mar 01, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-toolbelt`                          This is just a collection of utilities for pytest, but don't really belong in pytest proper.                                                                              Aug 12, 2019    3 - Alpha              N/A
-   :pypi:`pytest-toolbox`                           Numerous useful plugins for pytest.                                                                                                                                       Apr 07, 2018    N/A                    pytest (>=3.5.0)
-   :pypi:`pytest-tornado`                           A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications.                                                                 Jun 17, 2020    5 - Production/Stable  pytest (>=3.6)
-   :pypi:`pytest-tornado5`                          A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications.                                                                 Nov 16, 2018    5 - Production/Stable  pytest (>=3.6)
-   :pypi:`pytest-tornado-yen3`                      A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications.                                                                 Oct 15, 2018    5 - Production/Stable  N/A
-   :pypi:`pytest-tornasync`                         py.test plugin for testing Python 3.5+ Tornado code                                                                                                                       Jul 15, 2019    3 - Alpha              pytest (>=3.0)
-   :pypi:`pytest-track`                                                                                                                                                                                                       Feb 26, 2021    3 - Alpha              pytest (>=3.0)
-   :pypi:`pytest-translations`                      Test your translation files.                                                                                                                                              Nov 05, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-travis-fold`                       Folds captured output sections in Travis CI build log                                                                                                                     Nov 29, 2017    4 - Beta               pytest (>=2.6.0)
-   :pypi:`pytest-trello`                            Plugin for py.test that integrates trello using markers                                                                                                                   Nov 20, 2015    5 - Production/Stable  N/A
-   :pypi:`pytest-trepan`                            Pytest plugin for trepan debugger.                                                                                                                                        Jul 28, 2018    5 - Production/Stable  N/A
-   :pypi:`pytest-trialtemp`                         py.test plugin for using the same _trial_temp working directory as trial                                                                                                  Jun 08, 2015    N/A                    N/A
-   :pypi:`pytest-trio`                              Pytest plugin for trio                                                                                                                                                    Oct 16, 2020    N/A                    N/A
-   :pypi:`pytest-tspwplib`                          A simple plugin to use with tspwplib                                                                                                                                      Jan 08, 2021    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-tstcls`                            Test Class Base                                                                                                                                                           Mar 23, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-twisted`                           A twisted plugin for pytest.                                                                                                                                              Aug 30, 2021    5 - Production/Stable  pytest (>=2.3)
-   :pypi:`pytest-typhoon-xray`                      Typhoon HIL plugin for pytest                                                                                                                                             Nov 03, 2021    4 - Beta               N/A
-   :pypi:`pytest-tytest`                            Typhoon HIL plugin for pytest                                                                                                                                             May 25, 2020    4 - Beta               pytest (>=5.4.2)
-   :pypi:`pytest-ubersmith`                         Easily mock calls to ubersmith at the \`requests\` level.                                                                                                                 Apr 13, 2015    N/A                    N/A
-   :pypi:`pytest-ui`                                Text User Interface for running python tests                                                                                                                              Jul 05, 2021    4 - Beta               pytest
-   :pypi:`pytest-unhandled-exception-exit-code`     Plugin for py.test set a different exit code on uncaught exceptions                                                                                                       Jun 22, 2020    5 - Production/Stable  pytest (>=2.3)
-   :pypi:`pytest-unittest-filter`                   A pytest plugin for filtering unittest-based test classes                                                                                                                 Jan 12, 2019    4 - Beta               pytest (>=3.1.0)
-   :pypi:`pytest-unmarked`                          Run only unmarked tests                                                                                                                                                   Aug 27, 2019    5 - Production/Stable  N/A
-   :pypi:`pytest-unordered`                         Test equality of unordered collections in pytest                                                                                                                          Mar 28, 2021    4 - Beta               N/A
-   :pypi:`pytest-upload-report`                     pytest-upload-report is a plugin for pytest that upload your test report for test results.                                                                                Jun 18, 2021    5 - Production/Stable  N/A
-   :pypi:`pytest-utils`                             Some helpers for pytest.                                                                                                                                                  Dec 04, 2021    4 - Beta               pytest (>=6.2.5,<7.0.0)
-   :pypi:`pytest-vagrant`                           A py.test plugin providing access to vagrant.                                                                                                                             Sep 07, 2021    5 - Production/Stable  pytest
-   :pypi:`pytest-valgrind`                                                                                                                                                                                                    May 19, 2021    N/A                    N/A
-   :pypi:`pytest-variables`                         pytest plugin for providing variables to tests/fixtures                                                                                                                   Oct 23, 2019    5 - Production/Stable  pytest (>=2.4.2)
-   :pypi:`pytest-variant`                           Variant support for Pytest                                                                                                                                                Jun 20, 2021    N/A                    N/A
-   :pypi:`pytest-vcr`                               Plugin for managing VCR.py cassettes                                                                                                                                      Apr 26, 2019    5 - Production/Stable  pytest (>=3.6.0)
-   :pypi:`pytest-vcr-delete-on-fail`                A pytest plugin that automates vcrpy cassettes deletion on test failure.                                                                                                  Aug 13, 2021    4 - Beta               pytest (>=6.2.2,<7.0.0)
-   :pypi:`pytest-vcrpandas`                         Test from HTTP interactions to dataframe processed.                                                                                                                       Jan 12, 2019    4 - Beta               pytest
-   :pypi:`pytest-venv`                              py.test fixture for creating a virtual environment                                                                                                                        Aug 04, 2020    4 - Beta               pytest
-   :pypi:`pytest-ver`                               Pytest module with Verification Report                                                                                                                                    Aug 30, 2021    2 - Pre-Alpha          N/A
-   :pypi:`pytest-verbose-parametrize`               More descriptive output for parametrized py.test tests                                                                                                                    May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-vimqf`                             A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window.                                                                         Feb 08, 2021    4 - Beta               pytest (>=6.2.2,<7.0.0)
-   :pypi:`pytest-virtualenv`                        Virtualenv fixture for py.test                                                                                                                                            May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-voluptuous`                        Pytest plugin for asserting data against voluptuous schema.                                                                                                               Jun 09, 2020    N/A                    pytest
-   :pypi:`pytest-vscodedebug`                       A pytest plugin to easily enable debugging tests within Visual Studio Code                                                                                                Dec 04, 2020    4 - Beta               N/A
-   :pypi:`pytest-vts`                               pytest plugin for automatic recording of http stubbed tests                                                                                                               Jun 05, 2019    N/A                    pytest (>=2.3)
-   :pypi:`pytest-vw`                                pytest-vw makes your failing test cases succeed under CI tools scrutiny                                                                                                   Oct 07, 2015    4 - Beta               N/A
-   :pypi:`pytest-vyper`                             Plugin for the vyper smart contract language.                                                                                                                             May 28, 2020    2 - Pre-Alpha          N/A
-   :pypi:`pytest-wa-e2e-plugin`                     Pytest plugin for testing whatsapp bots with end to end tests                                                                                                             Feb 18, 2020    4 - Beta               pytest (>=3.5.0)
-   :pypi:`pytest-watch`                             Local continuous test runner with pytest and watchdog.                                                                                                                    May 20, 2018    N/A                    N/A
-   :pypi:`pytest-watcher`                           Continiously runs pytest on changes in \*.py files                                                                                                                        Sep 18, 2021    3 - Alpha              N/A
-   :pypi:`pytest-wdl`                               Pytest plugin for testing WDL workflows.                                                                                                                                  Nov 17, 2020    5 - Production/Stable  N/A
-   :pypi:`pytest-webdriver`                         Selenium webdriver fixture for py.test                                                                                                                                    May 28, 2019    5 - Production/Stable  pytest
-   :pypi:`pytest-wetest`                            Welian API Automation test framework pytest plugin                                                                                                                        Nov 10, 2018    4 - Beta               N/A
-   :pypi:`pytest-whirlwind`                         Testing Tornado.                                                                                                                                                          Jun 12, 2020    N/A                    N/A
-   :pypi:`pytest-wholenodeid`                       pytest addon for displaying the whole node id for failures                                                                                                                Aug 26, 2015    4 - Beta               pytest (>=2.0)
-   :pypi:`pytest-win32consoletitle`                 Pytest progress in console title (Win32 only)                                                                                                                             Aug 08, 2021    N/A                    N/A
-   :pypi:`pytest-winnotify`                         Windows tray notifications for py.test results.                                                                                                                           Apr 22, 2016    N/A                    N/A
-   :pypi:`pytest-with-docker`                       pytest with docker helpers.                                                                                                                                               Nov 09, 2021    N/A                    pytest
-   :pypi:`pytest-workflow`                          A pytest plugin for configuring workflow/pipeline tests using YAML files                                                                                                  Dec 03, 2021    5 - Production/Stable  pytest (>=5.4.0)
-   :pypi:`pytest-xdist`                             pytest xdist plugin for distributed testing and loop-on-failing modes                                                                                                     Sep 21, 2021    5 - Production/Stable  pytest (>=6.0.0)
-   :pypi:`pytest-xdist-debug-for-graingert`         pytest xdist plugin for distributed testing and loop-on-failing modes                                                                                                     Jul 24, 2019    5 - Production/Stable  pytest (>=4.4.0)
-   :pypi:`pytest-xdist-forked`                      forked from pytest-xdist                                                                                                                                                  Feb 10, 2020    5 - Production/Stable  pytest (>=4.4.0)
-   :pypi:`pytest-xdist-tracker`                     pytest plugin helps to reproduce failures for particular xdist node                                                                                                       Nov 18, 2021    3 - Alpha              pytest (>=3.5.1)
-   :pypi:`pytest-xfaillist`                         Maintain a xfaillist in an additional file to avoid merge-conflicts.                                                                                                      Sep 17, 2021    N/A                    pytest (>=6.2.2,<7.0.0)
-   :pypi:`pytest-xfiles`                            Pytest fixtures providing data read from function, module or package related (x)files.                                                                                    Feb 27, 2018    N/A                    N/A
-   :pypi:`pytest-xlog`                              Extended logging for test and decorators                                                                                                                                  May 31, 2020    4 - Beta               N/A
-   :pypi:`pytest-xpara`                             An extended parametrizing plugin of pytest.                                                                                                                               Oct 30, 2017    3 - Alpha              pytest
-   :pypi:`pytest-xprocess`                          A pytest plugin for managing processes across test runs.                                                                                                                  Jul 28, 2021    4 - Beta               pytest (>=2.8)
-   :pypi:`pytest-xray`                                                                                                                                                                                                        May 30, 2019    3 - Alpha              N/A
-   :pypi:`pytest-xrayjira`                                                                                                                                                                                                    Mar 17, 2020    3 - Alpha              pytest (==4.3.1)
-   :pypi:`pytest-xray-server`                                                                                                                                                                                                 Oct 27, 2021    3 - Alpha              pytest (>=5.3.1)
-   :pypi:`pytest-xvfb`                              A pytest plugin to run Xvfb for tests.                                                                                                                                    Jun 09, 2020    4 - Beta               pytest (>=2.8.1)
-   :pypi:`pytest-yaml`                              This plugin is used to load yaml output to your test using pytest framework.                                                                                              Oct 05, 2018    N/A                    pytest
-   :pypi:`pytest-yamltree`                          Create or check file/directory trees described by YAML                                                                                                                    Mar 02, 2020    4 - Beta               pytest (>=3.1.1)
-   :pypi:`pytest-yamlwsgi`                          Run tests against wsgi apps defined in yaml                                                                                                                               May 11, 2010    N/A                    N/A
-   :pypi:`pytest-yapf`                              Run yapf                                                                                                                                                                  Jul 06, 2017    4 - Beta               pytest (>=3.1.1)
-   :pypi:`pytest-yapf3`                             Validate your Python file format with yapf                                                                                                                                Aug 03, 2020    5 - Production/Stable  pytest (>=5.4)
-   :pypi:`pytest-yield`                             PyTest plugin to run tests concurrently, each \`yield\` switch context to other one                                                                                       Jan 23, 2019    N/A                    N/A
-   :pypi:`pytest-yuk`                               Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk.                                                                                    Mar 26, 2021    N/A                    N/A
-   :pypi:`pytest-zafira`                            A Zafira plugin for pytest                                                                                                                                                Sep 18, 2019    5 - Production/Stable  pytest (==4.1.1)
-   :pypi:`pytest-zap`                               OWASP ZAP plugin for py.test.                                                                                                                                             May 12, 2014    4 - Beta               N/A
-   :pypi:`pytest-zebrunner`                         Pytest connector for Zebrunner reporting                                                                                                                                  Dec 02, 2021    5 - Production/Stable  pytest (>=4.5.0)
-   :pypi:`pytest-zigzag`                            Extend py.test for RPC OpenStack testing.                                                                                                                                 Feb 27, 2019    4 - Beta               pytest (~=3.6)
-   ===============================================  ========================================================================================================================================================================  ==============  =====================  ================================================
+   ===============================================  ======================================================================================================================================================================================================================================================================================================================================================================================  ==============  =====================  ================================================
+   name                                             summary                                                                                                                                                                                                                                                                                                                                                                                 last_release    status                 requires
+   ===============================================  ======================================================================================================================================================================================================================================================================================================================================================================================  ==============  =====================  ================================================
+   :pypi:`databricks-labs-pytester`                 Python Testing for Databricks                                                                                                                                                                                                                                                                                                                                                           Feb 27, 2025    4 - Beta               pytest>=8.3
+   :pypi:`logassert`                                Simple but powerful assertion and verification of logged lines                                                                                                                                                                                                                                                                                                                          Jan 29, 2025    5 - Production/Stable  pytest; extra == "dev"
+   :pypi:`logot`                                    Test whether your code is logging correctly 🪵                                                                                                                                                                                                                                                                                                                                          Mar 23, 2024    5 - Production/Stable  pytest<9,>=7; extra == "pytest"
+   :pypi:`nuts`                                     Network Unit Testing System                                                                                                                                                                                                                                                                                                                                                             Jul 19, 2024    N/A                    pytest<8,>=7
+   :pypi:`pytest-abq`                               Pytest integration for the ABQ universal test runner.                                                                                                                                                                                                                                                                                                                                   Apr 07, 2023    N/A                    N/A
+   :pypi:`pytest-abstracts`                         A contextmanager pytest fixture for handling multiple mock abstracts                                                                                                                                                                                                                                                                                                                    May 25, 2022    N/A                    N/A
+   :pypi:`pytest-accept`                            A pytest-plugin for updating doctest outputs                                                                                                                                                                                                                                                                                                                                            Dec 08, 2024    N/A                    pytest>=7
+   :pypi:`pytest-adaptavist`                        pytest plugin for generating test execution results within Jira Test Management (tm4j)                                                                                                                                                                                                                                                                                                  Oct 13, 2022    N/A                    pytest (>=5.4.0)
+   :pypi:`pytest-adaptavist-fixed`                  pytest plugin for generating test execution results within Jira Test Management (tm4j)                                                                                                                                                                                                                                                                                                  Jan 17, 2025    N/A                    pytest>=5.4.0
+   :pypi:`pytest-addons-test`                       用于测试pytest的插件                                                                                                                                                                                                                                                                                                                                                                    Aug 02, 2021    N/A                    pytest (>=6.2.4,<7.0.0)
+   :pypi:`pytest-adf`                               Pytest plugin for writing Azure Data Factory integration tests                                                                                                                                                                                                                                                                                                                          May 10, 2021    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-adf-azure-identity`                Pytest plugin for writing Azure Data Factory integration tests                                                                                                                                                                                                                                                                                                                          Mar 06, 2021    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-ads-testplan`                      Azure DevOps Test Case reporting for pytest tests                                                                                                                                                                                                                                                                                                                                       Sep 15, 2022    N/A                    N/A
+   :pypi:`pytest-affected`                                                                                                                                                                                                                                                                                                                                                                                                                  Nov 06, 2023    N/A                    N/A
+   :pypi:`pytest-agent`                             Service that exposes a REST API that can be used to interract remotely with Pytest. It is shipped with a dashboard that enables running tests in a more convenient way.                                                                                                                                                                                                                 Nov 25, 2021    N/A                    N/A
+   :pypi:`pytest-aggreport`                         pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details.                                                                                                                                                                                                                                                               Mar 07, 2021    4 - Beta               pytest (>=6.2.2)
+   :pypi:`pytest-ai`                                A Python package to generate regular, edge-case, and security HTTP tests.                                                                                                                                                                                                                                                                                                               Jan 22, 2025    N/A                    N/A
+   :pypi:`pytest-ai1899`                            pytest plugin for connecting to ai1899 smart system stack                                                                                                                                                                                                                                                                                                                               Mar 13, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-aio`                               Pytest plugin for testing async python code                                                                                                                                                                                                                                                                                                                                             Jul 31, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-aioboto3`                          Aioboto3 Pytest with Moto                                                                                                                                                                                                                                                                                                                                                               Jan 17, 2025    N/A                    N/A
+   :pypi:`pytest-aiofiles`                          pytest fixtures for writing aiofiles tests with pyfakefs                                                                                                                                                                                                                                                                                                                                May 14, 2017    5 - Production/Stable  N/A
+   :pypi:`pytest-aiogram`                                                                                                                                                                                                                                                                                                                                                                                                                   May 06, 2023    N/A                    N/A
+   :pypi:`pytest-aiohttp`                           Pytest plugin for aiohttp support                                                                                                                                                                                                                                                                                                                                                       Jan 23, 2025    4 - Beta               pytest>=6.1.0
+   :pypi:`pytest-aiohttp-client`                    Pytest \`client\` fixture for the Aiohttp                                                                                                                                                                                                                                                                                                                                               Jan 10, 2023    N/A                    pytest (>=7.2.0,<8.0.0)
+   :pypi:`pytest-aiomoto`                           pytest-aiomoto                                                                                                                                                                                                                                                                                                                                                                          Jun 24, 2023    N/A                    pytest (>=7.0,<8.0)
+   :pypi:`pytest-aioresponses`                      py.test integration for aioresponses                                                                                                                                                                                                                                                                                                                                                    Jan 02, 2025    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-aioworkers`                        A plugin to test aioworkers project with pytest                                                                                                                                                                                                                                                                                                                                         Dec 26, 2024    5 - Production/Stable  pytest>=8.3.4
+   :pypi:`pytest-airflow`                           pytest support for airflow.                                                                                                                                                                                                                                                                                                                                                             Apr 03, 2019    3 - Alpha              pytest (>=4.4.0)
+   :pypi:`pytest-airflow-utils`                                                                                                                                                                                                                                                                                                                                                                                                             Nov 15, 2021    N/A                    N/A
+   :pypi:`pytest-alembic`                           A pytest plugin for verifying alembic migrations.                                                                                                                                                                                                                                                                                                                                       Jul 29, 2024    N/A                    pytest>=6.0
+   :pypi:`pytest-alerts`                            A pytest plugin for sending test results to Slack and Telegram                                                                                                                                                                                                                                                                                                                          Feb 21, 2025    4 - Beta               pytest>=7.4.0
+   :pypi:`pytest-allclose`                          Pytest fixture extending Numpy's allclose function                                                                                                                                                                                                                                                                                                                                      Jul 30, 2019    5 - Production/Stable  pytest
+   :pypi:`pytest-allure-adaptor`                    Plugin for py.test to generate allure xml reports                                                                                                                                                                                                                                                                                                                                       Jan 10, 2018    N/A                    pytest (>=2.7.3)
+   :pypi:`pytest-allure-adaptor2`                   Plugin for py.test to generate allure xml reports                                                                                                                                                                                                                                                                                                                                       Oct 14, 2020    N/A                    pytest (>=2.7.3)
+   :pypi:`pytest-allure-collection`                 pytest plugin to collect allure markers without running any tests                                                                                                                                                                                                                                                                                                                       Apr 13, 2023    N/A                    pytest
+   :pypi:`pytest-allure-dsl`                        pytest plugin to test case doc string dls instructions                                                                                                                                                                                                                                                                                                                                  Oct 25, 2020    4 - Beta               pytest
+   :pypi:`pytest-allure-id2history`                 Overwrite allure history id with testcase full name and testcase id if testcase has id, exclude parameters.                                                                                                                                                                                                                                                                             May 14, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-allure-intersection`                                                                                                                                                                                                                                                                                                                                                                                                       Oct 27, 2022    N/A                    pytest (<5)
+   :pypi:`pytest-allure-spec-coverage`              The pytest plugin aimed to display test coverage of the specs(requirements) in Allure                                                                                                                                                                                                                                                                                                   Oct 26, 2021    N/A                    pytest
+   :pypi:`pytest-alphamoon`                         Static code checks used at Alphamoon                                                                                                                                                                                                                                                                                                                                                    Dec 30, 2021    5 - Production/Stable  pytest (>=3.5.0)
+   :pypi:`pytest-amaranth-sim`                      Fixture to automate running Amaranth simulations                                                                                                                                                                                                                                                                                                                                        Sep 21, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-analyzer`                          this plugin allows to analyze tests in pytest project, collect test metadata and sync it with testomat.io TCM system                                                                                                                                                                                                                                                                    Feb 21, 2024    N/A                    pytest <8.0.0,>=7.3.1
+   :pypi:`pytest-android`                           This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2.                                                                                                                                                                                                                                                                                          Feb 21, 2019    3 - Alpha              pytest
+   :pypi:`pytest-anki`                              A pytest plugin for testing Anki add-ons                                                                                                                                                                                                                                                                                                                                                Jul 31, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-annotate`                          pytest-annotate: Generate PyAnnotate annotations from your pytest tests.                                                                                                                                                                                                                                                                                                                Jun 07, 2022    3 - Alpha              pytest (<8.0.0,>=3.2.0)
+   :pypi:`pytest-annotated`                         Pytest plugin to allow use of Annotated in tests to resolve fixtures                                                                                                                                                                                                                                                                                                                    Sep 30, 2024    N/A                    pytest>=8.3.3
+   :pypi:`pytest-ansible`                           Plugin for pytest to simplify calling ansible modules from tests or fixtures                                                                                                                                                                                                                                                                                                            Apr 01, 2025    5 - Production/Stable  pytest>=6
+   :pypi:`pytest-ansible-playbook`                  Pytest fixture which runs given ansible playbook file.                                                                                                                                                                                                                                                                                                                                  Mar 08, 2019    4 - Beta               N/A
+   :pypi:`pytest-ansible-playbook-runner`           Pytest fixture which runs given ansible playbook file.                                                                                                                                                                                                                                                                                                                                  Dec 02, 2020    4 - Beta               pytest (>=3.1.0)
+   :pypi:`pytest-ansible-units`                     A pytest plugin for running unit tests within an ansible collection                                                                                                                                                                                                                                                                                                                     Apr 14, 2022    N/A                    N/A
+   :pypi:`pytest-antilru`                           Bust functools.lru_cache when running pytest to avoid test pollution                                                                                                                                                                                                                                                                                                                    Jul 28, 2024    5 - Production/Stable  pytest>=7; python_version >= "3.10"
+   :pypi:`pytest-anyio`                             The pytest anyio plugin is built into anyio. You don't need this package.                                                                                                                                                                                                                                                                                                               Jun 29, 2021    N/A                    pytest
+   :pypi:`pytest-anything`                          Pytest fixtures to assert anything and something                                                                                                                                                                                                                                                                                                                                        Jan 18, 2024    N/A                    pytest
+   :pypi:`pytest-aoc`                               Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures                                                                                                                                                                                                                                                                                                              Dec 02, 2023    5 - Production/Stable  pytest ; extra == 'test'
+   :pypi:`pytest-aoreporter`                        pytest report                                                                                                                                                                                                                                                                                                                                                                           Jun 27, 2022    N/A                    N/A
+   :pypi:`pytest-api`                               An ASGI middleware to populate OpenAPI Specification examples from pytest functions                                                                                                                                                                                                                                                                                                     May 12, 2022    N/A                    pytest (>=7.1.1,<8.0.0)
+   :pypi:`pytest-api-soup`                          Validate multiple endpoints with unit testing using a single source of truth.                                                                                                                                                                                                                                                                                                           Aug 27, 2022    N/A                    N/A
+   :pypi:`pytest-apistellar`                        apistellar plugin for pytest.                                                                                                                                                                                                                                                                                                                                                           Jun 18, 2019    N/A                    N/A
+   :pypi:`pytest-apiver`                                                                                                                                                                                                                                                                                                                                                                                                                    Jun 21, 2024    N/A                    pytest
+   :pypi:`pytest-appengine`                         AppEngine integration that works well with pytest-django                                                                                                                                                                                                                                                                                                                                Feb 27, 2017    N/A                    N/A
+   :pypi:`pytest-appium`                            Pytest plugin for appium                                                                                                                                                                                                                                                                                                                                                                Dec 05, 2019    N/A                    N/A
+   :pypi:`pytest-approvaltests`                     A plugin to use approvaltests with pytest                                                                                                                                                                                                                                                                                                                                               May 08, 2022    4 - Beta               pytest (>=7.0.1)
+   :pypi:`pytest-approvaltests-geo`                 Extension for ApprovalTests.Python specific to geo data verification                                                                                                                                                                                                                                                                                                                    Feb 05, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-archon`                            Rule your architecture like a real developer                                                                                                                                                                                                                                                                                                                                            Dec 18, 2023    5 - Production/Stable  pytest >=7.2
+   :pypi:`pytest-argus`                             pyest results colection plugin                                                                                                                                                                                                                                                                                                                                                          Jun 24, 2021    5 - Production/Stable  pytest (>=6.2.4)
+   :pypi:`pytest-argus-server`                      A plugin that provides a running Argus API server for tests                                                                                                                                                                                                                                                                                                                             Mar 24, 2025    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-arraydiff`                         pytest plugin to help with comparing array output from tests                                                                                                                                                                                                                                                                                                                            Nov 27, 2023    4 - Beta               pytest >=4.6
+   :pypi:`pytest-asgi-server`                       Convenient ASGI client/server fixtures for Pytest                                                                                                                                                                                                                                                                                                                                       Dec 12, 2020    N/A                    pytest (>=5.4.1)
+   :pypi:`pytest-aspec`                             A rspec format reporter for pytest                                                                                                                                                                                                                                                                                                                                                      Dec 20, 2023    4 - Beta               N/A
+   :pypi:`pytest-asptest`                           test Answer Set Programming programs                                                                                                                                                                                                                                                                                                                                                    Apr 28, 2018    4 - Beta               N/A
+   :pypi:`pytest-assertcount`                       Plugin to count actual number of asserts in pytest                                                                                                                                                                                                                                                                                                                                      Oct 23, 2022    N/A                    pytest (>=5.0.0)
+   :pypi:`pytest-assertions`                        Pytest Assertions                                                                                                                                                                                                                                                                                                                                                                       Apr 27, 2022    N/A                    N/A
+   :pypi:`pytest-assertutil`                        pytest-assertutil                                                                                                                                                                                                                                                                                                                                                                       May 10, 2019    N/A                    N/A
+   :pypi:`pytest-assert-utils`                      Useful assertion utilities for use with pytest                                                                                                                                                                                                                                                                                                                                          Apr 14, 2022    3 - Alpha              N/A
+   :pypi:`pytest-assist`                            load testing library                                                                                                                                                                                                                                                                                                                                                                    Mar 17, 2025    N/A                    pytest
+   :pypi:`pytest-assume`                            A pytest plugin that allows multiple failures per test                                                                                                                                                                                                                                                                                                                                  Jun 24, 2021    N/A                    pytest (>=2.7)
+   :pypi:`pytest-assurka`                           A pytest plugin for Assurka Studio                                                                                                                                                                                                                                                                                                                                                      Aug 04, 2022    N/A                    N/A
+   :pypi:`pytest-ast-back-to-python`                A plugin for pytest devs to view how assertion rewriting recodes the AST                                                                                                                                                                                                                                                                                                                Sep 29, 2019    4 - Beta               N/A
+   :pypi:`pytest-asteroid`                          PyTest plugin for docker-based testing on database images                                                                                                                                                                                                                                                                                                                               Aug 15, 2022    N/A                    pytest (>=6.2.5,<8.0.0)
+   :pypi:`pytest-astropy`                           Meta-package containing dependencies for testing                                                                                                                                                                                                                                                                                                                                        Sep 26, 2023    5 - Production/Stable  pytest >=4.6
+   :pypi:`pytest-astropy-header`                    pytest plugin to add diagnostic information to the header of the test output                                                                                                                                                                                                                                                                                                            Sep 06, 2022    3 - Alpha              pytest (>=4.6)
+   :pypi:`pytest-ast-transformer`                                                                                                                                                                                                                                                                                                                                                                                                           May 04, 2019    3 - Alpha              pytest
+   :pypi:`pytest_async`                             pytest-async - Run your coroutine in event loop without decorator                                                                                                                                                                                                                                                                                                                       Feb 26, 2020    N/A                    N/A
+   :pypi:`pytest-async-generators`                  Pytest fixtures for async generators                                                                                                                                                                                                                                                                                                                                                    Jul 05, 2023    N/A                    N/A
+   :pypi:`pytest-asyncio`                           Pytest support for asyncio                                                                                                                                                                                                                                                                                                                                                              Mar 25, 2025    4 - Beta               pytest<9,>=8.2
+   :pypi:`pytest-asyncio-concurrent`                Pytest plugin to execute python async tests concurrently.                                                                                                                                                                                                                                                                                                                               Mar 16, 2025    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-asyncio-cooperative`               Run all your asynchronous tests cooperatively.                                                                                                                                                                                                                                                                                                                                          Jul 04, 2024    N/A                    N/A
+   :pypi:`pytest-asyncio-network-simulator`         pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests                                                                                                                                                                                                                                                                                                  Jul 31, 2018    3 - Alpha              pytest (<3.7.0,>=3.3.2)
+   :pypi:`pytest-async-mongodb`                     pytest plugin for async MongoDB                                                                                                                                                                                                                                                                                                                                                         Oct 18, 2017    5 - Production/Stable  pytest (>=2.5.2)
+   :pypi:`pytest-async-sqlalchemy`                  Database testing fixtures using the SQLAlchemy asyncio API                                                                                                                                                                                                                                                                                                                              Oct 07, 2021    4 - Beta               pytest (>=6.0.0)
+   :pypi:`pytest-atf-allure`                        基于allure-pytest进行自定义                                                                                                                                                                                                                                                                                                                                                             Nov 29, 2023    N/A                    pytest (>=7.4.2,<8.0.0)
+   :pypi:`pytest-atomic`                            Skip rest of tests if previous test failed.                                                                                                                                                                                                                                                                                                                                             Nov 24, 2018    4 - Beta               N/A
+   :pypi:`pytest-atstack`                           A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Jan 02, 2025    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-attrib`                            pytest plugin to select tests based on attributes similar to the nose-attrib plugin                                                                                                                                                                                                                                                                                                     May 24, 2016    4 - Beta               N/A
+   :pypi:`pytest-attributes`                        A plugin that allows users to add attributes to their tests. These attributes can then be referenced by fixtures or the test itself.                                                                                                                                                                                                                                                    Jun 24, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-austin`                            Austin plugin for pytest                                                                                                                                                                                                                                                                                                                                                                Oct 11, 2020    4 - Beta               N/A
+   :pypi:`pytest-autocap`                           automatically capture test & fixture stdout/stderr to files                                                                                                                                                                                                                                                                                                                             May 15, 2022    N/A                    pytest (<7.2,>=7.1.2)
+   :pypi:`pytest-autochecklog`                      automatically check condition and log all the checks                                                                                                                                                                                                                                                                                                                                    Apr 25, 2015    4 - Beta               N/A
+   :pypi:`pytest-autofixture`                       simplify pytest fixtures                                                                                                                                                                                                                                                                                                                                                                Aug 01, 2024    N/A                    pytest>=8
+   :pypi:`pytest-automation`                        pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality.                                                                                                                                                                                                                                                                                  Apr 24, 2024    N/A                    pytest>=7.0.0
+   :pypi:`pytest-automock`                          Pytest plugin for automatical mocks creation                                                                                                                                                                                                                                                                                                                                            May 16, 2023    N/A                    pytest ; extra == 'dev'
+   :pypi:`pytest-auto-parametrize`                  pytest plugin: avoid repeating arguments in parametrize                                                                                                                                                                                                                                                                                                                                 Oct 02, 2016    3 - Alpha              N/A
+   :pypi:`pytest-autotest`                          This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2.                                                                                                                                                                                                                                                                                          Aug 25, 2021    N/A                    pytest
+   :pypi:`pytest-aviator`                           Aviator's Flakybot pytest plugin that automatically reruns flaky tests.                                                                                                                                                                                                                                                                                                                 Nov 04, 2022    4 - Beta               pytest
+   :pypi:`pytest-avoidance`                         Makes pytest skip tests that don not need rerunning                                                                                                                                                                                                                                                                                                                                     May 23, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-aws`                               pytest plugin for testing AWS resource configurations                                                                                                                                                                                                                                                                                                                                   Oct 04, 2017    4 - Beta               N/A
+   :pypi:`pytest-aws-apigateway`                    pytest plugin for AWS ApiGateway                                                                                                                                                                                                                                                                                                                                                        May 24, 2024    4 - Beta               pytest
+   :pypi:`pytest-aws-config`                        Protect your AWS credentials in unit tests                                                                                                                                                                                                                                                                                                                                              May 28, 2021    N/A                    N/A
+   :pypi:`pytest-aws-fixtures`                      A series of fixtures to use in integration tests involving actual AWS services.                                                                                                                                                                                                                                                                                                         Apr 04, 2025    N/A                    pytest<9.0.0,>=8.0.0
+   :pypi:`pytest-axe`                               pytest plugin for axe-selenium-python                                                                                                                                                                                                                                                                                                                                                   Nov 12, 2018    N/A                    pytest (>=3.0.0)
+   :pypi:`pytest-axe-playwright-snapshot`           A pytest plugin that runs Axe-core on Playwright pages and takes snapshots of the results.                                                                                                                                                                                                                                                                                              Jul 25, 2023    N/A                    pytest
+   :pypi:`pytest-azure`                             Pytest utilities and mocks for Azure                                                                                                                                                                                                                                                                                                                                                    Jan 18, 2023    3 - Alpha              pytest
+   :pypi:`pytest-azure-devops`                      Simplifies using azure devops parallel strategy (https://docs.microsoft.com/en-us/azure/devops/pipelines/test/parallel-testing-any-test-runner) with pytest.                                                                                                                                                                                                                            Jun 20, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-azurepipelines`                    Formatting PyTest output for Azure Pipelines UI                                                                                                                                                                                                                                                                                                                                         Oct 06, 2023    5 - Production/Stable  pytest (>=5.0.0)
+   :pypi:`pytest-bandit`                            A bandit plugin for pytest                                                                                                                                                                                                                                                                                                                                                              Feb 23, 2021    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-bandit-xayon`                      A bandit plugin for pytest                                                                                                                                                                                                                                                                                                                                                              Oct 17, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-base-url`                          pytest plugin for URL based testing                                                                                                                                                                                                                                                                                                                                                     Jan 31, 2024    5 - Production/Stable  pytest>=7.0.0
+   :pypi:`pytest-batch-regression`                  A pytest plugin to repeat the entire test suite in batches.                                                                                                                                                                                                                                                                                                                             May 08, 2024    N/A                    pytest>=6.0.0
+   :pypi:`pytest-bazel`                             A pytest runner with bazel support                                                                                                                                                                                                                                                                                                                                                      Sep 27, 2024    4 - Beta               pytest
+   :pypi:`pytest-bdd`                               BDD for pytest                                                                                                                                                                                                                                                                                                                                                                          Dec 05, 2024    6 - Mature             pytest>=7.0.0
+   :pypi:`pytest-bdd-html`                          pytest plugin to display BDD info in HTML test report                                                                                                                                                                                                                                                                                                                                   Nov 22, 2022    3 - Alpha              pytest (!=6.0.0,>=5.0)
+   :pypi:`pytest-bdd-ng`                            BDD for pytest                                                                                                                                                                                                                                                                                                                                                                          Nov 26, 2024    4 - Beta               pytest>=5.2
+   :pypi:`pytest-bdd-report`                        A pytest-bdd plugin for generating useful and informative BDD test reports                                                                                                                                                                                                                                                                                                              Nov 27, 2024    N/A                    pytest>=7.1.3
+   :pypi:`pytest-bdd-splinter`                      Common steps for pytest bdd and splinter integration                                                                                                                                                                                                                                                                                                                                    Aug 12, 2019    5 - Production/Stable  pytest (>=4.0.0)
+   :pypi:`pytest-bdd-web`                           A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Jan 02, 2020    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-bdd-wrappers`                                                                                                                                                                                                                                                                                                                                                                                                              Feb 11, 2020    2 - Pre-Alpha          N/A
+   :pypi:`pytest-beakerlib`                         A pytest plugin that reports test results to the BeakerLib framework                                                                                                                                                                                                                                                                                                                    Mar 17, 2017    5 - Production/Stable  pytest
+   :pypi:`pytest-beartype`                          Pytest plugin to run your tests with beartype checking enabled.                                                                                                                                                                                                                                                                                                                         Oct 31, 2024    N/A                    pytest
+   :pypi:`pytest-bec-e2e`                           BEC pytest plugin for end-to-end tests                                                                                                                                                                                                                                                                                                                                                  Apr 01, 2025    3 - Alpha              pytest
+   :pypi:`pytest-beds`                              Fixtures for testing Google Appengine (GAE) apps                                                                                                                                                                                                                                                                                                                                        Jun 07, 2016    4 - Beta               N/A
+   :pypi:`pytest-beeprint`                          use icdiff for better error messages in pytest assertions                                                                                                                                                                                                                                                                                                                               Jul 04, 2023    4 - Beta               N/A
+   :pypi:`pytest-bench`                             Benchmark utility that plugs into pytest.                                                                                                                                                                                                                                                                                                                                               Jul 21, 2014    3 - Alpha              N/A
+   :pypi:`pytest-benchmark`                         A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer.                                                                                                                                                                                                                                                            Oct 30, 2024    5 - Production/Stable  pytest>=8.1
+   :pypi:`pytest-better-datadir`                    A small example package                                                                                                                                                                                                                                                                                                                                                                 Mar 13, 2023    N/A                    N/A
+   :pypi:`pytest-better-parametrize`                Better description of parametrized test cases                                                                                                                                                                                                                                                                                                                                           Mar 05, 2024    4 - Beta               pytest >=6.2.0
+   :pypi:`pytest-bg-process`                        Pytest plugin to initialize background process                                                                                                                                                                                                                                                                                                                                          Jan 24, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-bigchaindb`                        A BigchainDB plugin for pytest.                                                                                                                                                                                                                                                                                                                                                         Jan 24, 2022    4 - Beta               N/A
+   :pypi:`pytest-bigquery-mock`                     Provides a mock fixture for python bigquery client                                                                                                                                                                                                                                                                                                                                      Dec 28, 2022    N/A                    pytest (>=5.0)
+   :pypi:`pytest-bisect-tests`                      Find tests leaking state and affecting other                                                                                                                                                                                                                                                                                                                                            Jun 09, 2024    N/A                    N/A
+   :pypi:`pytest-black`                             A pytest plugin to enable format checking with black                                                                                                                                                                                                                                                                                                                                    Dec 15, 2024    4 - Beta               pytest>=7.0.0
+   :pypi:`pytest-black-multipy`                     Allow '--black' on older Pythons                                                                                                                                                                                                                                                                                                                                                        Jan 14, 2021    5 - Production/Stable  pytest (!=3.7.3,>=3.5) ; extra == 'testing'
+   :pypi:`pytest-black-ng`                          A pytest plugin to enable format checking with black                                                                                                                                                                                                                                                                                                                                    Oct 20, 2022    4 - Beta               pytest (>=7.0.0)
+   :pypi:`pytest-blame`                             A pytest plugin helps developers to debug by providing useful commits history.                                                                                                                                                                                                                                                                                                          May 04, 2019    N/A                    pytest (>=4.4.0)
+   :pypi:`pytest-blender`                           Blender Pytest plugin.                                                                                                                                                                                                                                                                                                                                                                  Aug 02, 2024    N/A                    pytest
+   :pypi:`pytest-blink1`                            Pytest plugin to emit notifications via the Blink(1) RGB LED                                                                                                                                                                                                                                                                                                                            Jan 07, 2018    4 - Beta               N/A
+   :pypi:`pytest-blockage`                          Disable network requests during a test run.                                                                                                                                                                                                                                                                                                                                             Dec 21, 2021    N/A                    pytest
+   :pypi:`pytest-blocker`                           pytest plugin to mark a test as blocker and skip all other tests                                                                                                                                                                                                                                                                                                                        Sep 07, 2015    4 - Beta               N/A
+   :pypi:`pytest-blue`                              A pytest plugin that adds a \`blue\` fixture for printing stuff in blue.                                                                                                                                                                                                                                                                                                                Sep 05, 2022    N/A                    N/A
+   :pypi:`pytest-board`                             Local continuous test runner with pytest and watchdog.                                                                                                                                                                                                                                                                                                                                  Jan 20, 2019    N/A                    N/A
+   :pypi:`pytest-boilerplate`                       The pytest plugin for your Django Boilerplate.                                                                                                                                                                                                                                                                                                                                          Sep 12, 2024    5 - Production/Stable  pytest>=4.0.0
+   :pypi:`pytest-boost-xml`                         Plugin for pytest to generate boost xml reports                                                                                                                                                                                                                                                                                                                                         Nov 30, 2022    4 - Beta               N/A
+   :pypi:`pytest-bootstrap`                                                                                                                                                                                                                                                                                                                                                                                                                 Mar 04, 2022    N/A                    N/A
+   :pypi:`pytest-boto-mock`                         Thin-wrapper around the mock package for easier use with pytest                                                                                                                                                                                                                                                                                                                         Jul 16, 2024    5 - Production/Stable  pytest>=8.2.0
+   :pypi:`pytest-bpdb`                              A py.test plug-in to enable drop to bpdb debugger on test failure.                                                                                                                                                                                                                                                                                                                      Jan 19, 2015    2 - Pre-Alpha          N/A
+   :pypi:`pytest-bq`                                BigQuery fixtures and fixture factories for Pytest.                                                                                                                                                                                                                                                                                                                                     May 08, 2024    5 - Production/Stable  pytest>=6.2
+   :pypi:`pytest-bravado`                           Pytest-bravado automatically generates from OpenAPI specification client fixtures.                                                                                                                                                                                                                                                                                                      Feb 15, 2022    N/A                    N/A
+   :pypi:`pytest-breakword`                         Use breakword with pytest                                                                                                                                                                                                                                                                                                                                                               Aug 04, 2021    N/A                    pytest (>=6.2.4,<7.0.0)
+   :pypi:`pytest-breed-adapter`                     A simple plugin to connect with breed-server                                                                                                                                                                                                                                                                                                                                            Nov 07, 2018    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-briefcase`                         A pytest plugin for running tests on a Briefcase project.                                                                                                                                                                                                                                                                                                                               Jun 14, 2020    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-broadcaster`                       Pytest plugin to broadcast pytest output to various destinations                                                                                                                                                                                                                                                                                                                        Mar 02, 2025    3 - Alpha              pytest
+   :pypi:`pytest-browser`                           A pytest plugin for console based browser test selection just after the collection phase                                                                                                                                                                                                                                                                                                Dec 10, 2016    3 - Alpha              N/A
+   :pypi:`pytest-browsermob-proxy`                  BrowserMob proxy plugin for py.test.                                                                                                                                                                                                                                                                                                                                                    Jun 11, 2013    4 - Beta               N/A
+   :pypi:`pytest_browserstack`                      Py.test plugin for BrowserStack                                                                                                                                                                                                                                                                                                                                                         Jan 27, 2016    4 - Beta               N/A
+   :pypi:`pytest-browserstack-local`                \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background.                                                                                                                                                                                                                                                                                                                  Feb 09, 2018    N/A                    N/A
+   :pypi:`pytest-budosystems`                       Budo Systems is a martial arts school management system. This module is the Budo Systems Pytest Plugin.                                                                                                                                                                                                                                                                                 May 07, 2023    3 - Alpha              pytest
+   :pypi:`pytest-bug`                               Pytest plugin for marking tests as a bug                                                                                                                                                                                                                                                                                                                                                Jun 05, 2024    5 - Production/Stable  pytest>=8.0.0
+   :pypi:`pytest-bugtong-tag`                       pytest-bugtong-tag is a plugin for pytest                                                                                                                                                                                                                                                                                                                                               Jan 16, 2022    N/A                    N/A
+   :pypi:`pytest-bugzilla`                          py.test bugzilla integration plugin                                                                                                                                                                                                                                                                                                                                                     May 05, 2010    4 - Beta               N/A
+   :pypi:`pytest-bugzilla-notifier`                 A plugin that allows you to execute create, update, and read information from BugZilla bugs                                                                                                                                                                                                                                                                                             Jun 15, 2018    4 - Beta               pytest (>=2.9.2)
+   :pypi:`pytest-buildkite`                         Plugin for pytest that automatically publishes coverage and pytest report annotations to Buildkite.                                                                                                                                                                                                                                                                                     Jul 13, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-builtin-types`                                                                                                                                                                                                                                                                                                                                                                                                             Nov 17, 2021    N/A                    pytest
+   :pypi:`pytest-bwrap`                             Run your tests in Bubblewrap sandboxes                                                                                                                                                                                                                                                                                                                                                  Feb 25, 2024    3 - Alpha              N/A
+   :pypi:`pytest-cache`                             pytest plugin with mechanisms for caching across test runs                                                                                                                                                                                                                                                                                                                              Jun 04, 2013    3 - Alpha              N/A
+   :pypi:`pytest-cache-assert`                      Cache assertion data to simplify regression testing of complex serializable data                                                                                                                                                                                                                                                                                                        Aug 14, 2023    5 - Production/Stable  pytest (>=6.0.0)
+   :pypi:`pytest-cagoule`                           Pytest plugin to only run tests affected by changes                                                                                                                                                                                                                                                                                                                                     Jan 01, 2020    3 - Alpha              N/A
+   :pypi:`pytest-cairo`                             Pytest support for cairo-lang and starknet                                                                                                                                                                                                                                                                                                                                              Apr 17, 2022    N/A                    pytest
+   :pypi:`pytest-call-checker`                      Small pytest utility to easily create test doubles                                                                                                                                                                                                                                                                                                                                      Oct 16, 2022    4 - Beta               pytest (>=7.1.3,<8.0.0)
+   :pypi:`pytest-camel-collect`                     Enable CamelCase-aware pytest class collection                                                                                                                                                                                                                                                                                                                                          Aug 02, 2020    N/A                    pytest (>=2.9)
+   :pypi:`pytest-canonical-data`                    A plugin which allows to compare results with canonical results, based on previous runs                                                                                                                                                                                                                                                                                                 May 08, 2020    2 - Pre-Alpha          pytest (>=3.5.0)
+   :pypi:`pytest-caprng`                            A plugin that replays pRNG state on failure.                                                                                                                                                                                                                                                                                                                                            May 02, 2018    4 - Beta               N/A
+   :pypi:`pytest-capsqlalchemy`                     Pytest plugin to allow capturing SQLAlchemy queries.                                                                                                                                                                                                                                                                                                                                    Mar 19, 2025    4 - Beta               N/A
+   :pypi:`pytest-capture-deprecatedwarnings`        pytest plugin to capture all deprecatedwarnings and put them in one file                                                                                                                                                                                                                                                                                                                Apr 30, 2019    N/A                    N/A
+   :pypi:`pytest-capture-warnings`                  pytest plugin to capture all warnings and put them in one file of your choice                                                                                                                                                                                                                                                                                                           May 03, 2022    N/A                    pytest
+   :pypi:`pytest-case`                              A clean, modern, wrapper for pytest.mark.parametrize                                                                                                                                                                                                                                                                                                                                    Nov 25, 2024    N/A                    pytest<9.0.0,>=8.3.3
+   :pypi:`pytest-cases`                             Separate test code from test cases in pytest.                                                                                                                                                                                                                                                                                                                                           Sep 26, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-cassandra`                         Cassandra CCM Test Fixtures for pytest                                                                                                                                                                                                                                                                                                                                                  Nov 04, 2017    1 - Planning           N/A
+   :pypi:`pytest-catchlog`                          py.test plugin to catch log messages. This is a fork of pytest-capturelog.                                                                                                                                                                                                                                                                                                              Jan 24, 2016    4 - Beta               pytest (>=2.6)
+   :pypi:`pytest-catch-server`                      Pytest plugin with server for catching HTTP requests.                                                                                                                                                                                                                                                                                                                                   Dec 12, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-cdist`                             A pytest plugin to split your test suite into multiple parts                                                                                                                                                                                                                                                                                                                            Jan 30, 2025    N/A                    pytest>=7
+   :pypi:`pytest-celery`                            Pytest plugin for Celery                                                                                                                                                                                                                                                                                                                                                                Feb 21, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-cfg-fetcher`                       Pass config options to your unit tests.                                                                                                                                                                                                                                                                                                                                                 Feb 26, 2024    N/A                    N/A
+   :pypi:`pytest-chainmaker`                        pytest plugin for chainmaker                                                                                                                                                                                                                                                                                                                                                            Oct 15, 2021    N/A                    N/A
+   :pypi:`pytest-chalice`                           A set of py.test fixtures for AWS Chalice                                                                                                                                                                                                                                                                                                                                               Jul 01, 2020    4 - Beta               N/A
+   :pypi:`pytest-change-assert`                     修改报错中文为英文                                                                                                                                                                                                                                                                                                                                                                      Oct 19, 2022    N/A                    N/A
+   :pypi:`pytest-change-demo`                       turn . into √,turn F into x                                                                                                                                                                                                                                                                                                                                                            Mar 02, 2022    N/A                    pytest
+   :pypi:`pytest-change-report`                     turn . into √,turn F into x                                                                                                                                                                                                                                                                                                                                                            Sep 14, 2020    N/A                    pytest
+   :pypi:`pytest-change-xds`                        turn . into √,turn F into x                                                                                                                                                                                                                                                                                                                                                            Apr 16, 2022    N/A                    pytest
+   :pypi:`pytest-chdir`                             A pytest fixture for changing current working directory                                                                                                                                                                                                                                                                                                                                 Jan 28, 2020    N/A                    pytest (>=5.0.0,<6.0.0)
+   :pypi:`pytest-check`                             A pytest plugin that allows multiple failures per test.                                                                                                                                                                                                                                                                                                                                 Apr 04, 2025    5 - Production/Stable  pytest>=7.0.0
+   :pypi:`pytest-checkdocs`                         check the README when running tests                                                                                                                                                                                                                                                                                                                                                     Apr 30, 2024    5 - Production/Stable  pytest!=8.1.*,>=6; extra == "testing"
+   :pypi:`pytest-checkipdb`                         plugin to check if there are ipdb debugs left                                                                                                                                                                                                                                                                                                                                           Dec 04, 2023    5 - Production/Stable  pytest >=2.9.2
+   :pypi:`pytest-check-library`                     check your missing library                                                                                                                                                                                                                                                                                                                                                              Jul 17, 2022    N/A                    N/A
+   :pypi:`pytest-check-libs`                        check your missing library                                                                                                                                                                                                                                                                                                                                                              Jul 17, 2022    N/A                    N/A
+   :pypi:`pytest-check-links`                       Check links in files                                                                                                                                                                                                                                                                                                                                                                    Jul 29, 2020    N/A                    pytest<9,>=7.0
+   :pypi:`pytest-checklist`                         Pytest plugin to track and report unit/function coverage.                                                                                                                                                                                                                                                                                                                               Jun 10, 2024    N/A                    N/A
+   :pypi:`pytest-check-mk`                          pytest plugin to test Check_MK checks                                                                                                                                                                                                                                                                                                                                                   Nov 19, 2015    4 - Beta               pytest
+   :pypi:`pytest-checkpoint`                        Restore a checkpoint in pytest                                                                                                                                                                                                                                                                                                                                                          Mar 30, 2025    N/A                    pytest>=8.0.0
+   :pypi:`pytest-check-requirements`                A package to prevent Dependency Confusion attacks against Yandex.                                                                                                                                                                                                                                                                                                                       Feb 20, 2024    N/A                    N/A
+   :pypi:`pytest-ch-framework`                      My pytest framework                                                                                                                                                                                                                                                                                                                                                                     Apr 17, 2024    N/A                    pytest==8.0.1
+   :pypi:`pytest-chic-report`                       Simple pytest plugin for generating and sending report to messengers.                                                                                                                                                                                                                                                                                                                   Nov 01, 2024    N/A                    pytest>=6.0
+   :pypi:`pytest-chinesereport`                                                                                                                                                                                                                                                                                                                                                                                                             Mar 19, 2025    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-choose`                            Provide the pytest with the ability to collect use cases based on rules in text files                                                                                                                                                                                                                                                                                                   Feb 04, 2024    N/A                    pytest >=7.0.0
+   :pypi:`pytest-chunks`                            Run only a chunk of your test suite                                                                                                                                                                                                                                                                                                                                                     Jul 05, 2022    N/A                    pytest (>=6.0.0)
+   :pypi:`pytest_cid`                               Compare data structures containing matching CIDs of different versions and encoding                                                                                                                                                                                                                                                                                                     Sep 01, 2023    4 - Beta               pytest >= 5.0, < 7.0
+   :pypi:`pytest-circleci`                          py.test plugin for CircleCI                                                                                                                                                                                                                                                                                                                                                             May 03, 2019    N/A                    N/A
+   :pypi:`pytest-circleci-parallelized`             Parallelize pytest across CircleCI workers.                                                                                                                                                                                                                                                                                                                                             Oct 20, 2022    N/A                    N/A
+   :pypi:`pytest-circleci-parallelized-rjp`         Parallelize pytest across CircleCI workers.                                                                                                                                                                                                                                                                                                                                             Jun 21, 2022    N/A                    pytest
+   :pypi:`pytest-ckan`                              Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8                                                                                                                                                                                                                                                                                                                             Apr 28, 2020    4 - Beta               pytest
+   :pypi:`pytest-clarity`                           A plugin providing an alternative, colourful diff output for failing assertions.                                                                                                                                                                                                                                                                                                        Jun 11, 2021    N/A                    N/A
+   :pypi:`pytest-class-fixtures`                    Class as PyTest fixtures (and BDD steps)                                                                                                                                                                                                                                                                                                                                                Nov 15, 2024    N/A                    pytest<9.0.0,>=8.3.3
+   :pypi:`pytest-cldf`                              Easy quality control for CLDF datasets using pytest                                                                                                                                                                                                                                                                                                                                     Nov 07, 2022    N/A                    pytest (>=3.6)
+   :pypi:`pytest-clean-database`                    A pytest plugin that cleans your database up after every test.                                                                                                                                                                                                                                                                                                                          Mar 14, 2025    3 - Alpha              pytest<9,>=7.0
+   :pypi:`pytest-cleanslate`                        Collects and executes pytest tests separately                                                                                                                                                                                                                                                                                                                                           Sep 04, 2024    N/A                    pytest
+   :pypi:`pytest_cleanup`                           Automated, comprehensive and well-organised pytest test cases.                                                                                                                                                                                                                                                                                                                          Jan 28, 2020    N/A                    N/A
+   :pypi:`pytest-cleanuptotal`                      A cleanup plugin for pytest                                                                                                                                                                                                                                                                                                                                                             Nov 08, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-clerk`                             A set of pytest fixtures to help with integration testing with Clerk.                                                                                                                                                                                                                                                                                                                   Jan 30, 2025    N/A                    pytest<9.0.0,>=8.0.0
+   :pypi:`pytest-cli2-ansible`                                                                                                                                                                                                                                                                                                                                                                                                              Mar 05, 2025    N/A                    N/A
+   :pypi:`pytest-click`                             Pytest plugin for Click                                                                                                                                                                                                                                                                                                                                                                 Feb 11, 2022    5 - Production/Stable  pytest (>=5.0)
+   :pypi:`pytest-cli-fixtures`                      Automatically register fixtures for custom CLI arguments                                                                                                                                                                                                                                                                                                                                Jul 28, 2022    N/A                    pytest (~=7.0)
+   :pypi:`pytest-clld`                                                                                                                                                                                                                                                                                                                                                                                                                      Oct 23, 2024    N/A                    pytest>=3.9
+   :pypi:`pytest-cloud`                             Distributed tests planner plugin for pytest testing framework.                                                                                                                                                                                                                                                                                                                          Oct 05, 2020    6 - Mature             N/A
+   :pypi:`pytest-cloudflare-worker`                 pytest plugin for testing cloudflare workers                                                                                                                                                                                                                                                                                                                                            Mar 30, 2021    4 - Beta               pytest (>=6.0.0)
+   :pypi:`pytest-cloudist`                          Distribute tests to cloud machines without fuss                                                                                                                                                                                                                                                                                                                                         Sep 02, 2022    4 - Beta               pytest (>=7.1.2,<8.0.0)
+   :pypi:`pytest-cmake`                             Provide CMake module for Pytest                                                                                                                                                                                                                                                                                                                                                         Feb 17, 2025    N/A                    pytest<9,>=4
+   :pypi:`pytest-cmake-presets`                     Execute CMake Presets via pytest                                                                                                                                                                                                                                                                                                                                                        Dec 26, 2022    N/A                    pytest (>=7.2.0,<8.0.0)
+   :pypi:`pytest-cmdline-add-args`                  Pytest plugin for custom argument handling and Allure reporting. This plugin allows you to add arguments before running a test.                                                                                                                                                                                                                                                         Sep 01, 2024    N/A                    N/A
+   :pypi:`pytest-cobra`                             PyTest plugin for testing Smart Contracts for Ethereum blockchain.                                                                                                                                                                                                                                                                                                                      Jun 29, 2019    3 - Alpha              pytest (<4.0.0,>=3.7.1)
+   :pypi:`pytest-cocotb`                            Pytest plugin to integrate Cocotb                                                                                                                                                                                                                                                                                                                                                       Mar 15, 2025    5 - Production/Stable  pytest; extra == "test"
+   :pypi:`pytest_codeblocks`                        Test code blocks in your READMEs                                                                                                                                                                                                                                                                                                                                                        Sep 17, 2023    5 - Production/Stable  pytest >= 7.0.0
+   :pypi:`pytest-codecarbon`                        Pytest plugin for measuring carbon emissions                                                                                                                                                                                                                                                                                                                                            Jun 15, 2022    N/A                    pytest
+   :pypi:`pytest-codecheckers`                      pytest plugin to add source code sanity checks (pep8 and friends)                                                                                                                                                                                                                                                                                                                       Feb 13, 2010    N/A                    N/A
+   :pypi:`pytest-codecov`                           Pytest plugin for uploading pytest-cov results to codecov.io                                                                                                                                                                                                                                                                                                                            Mar 25, 2025    4 - Beta               pytest>=4.6.0
+   :pypi:`pytest-codegen`                           Automatically create pytest test signatures                                                                                                                                                                                                                                                                                                                                             Aug 23, 2020    2 - Pre-Alpha          N/A
+   :pypi:`pytest-codeowners`                        Pytest plugin for selecting tests by GitHub CODEOWNERS.                                                                                                                                                                                                                                                                                                                                 Mar 30, 2022    4 - Beta               pytest (>=6.0.0)
+   :pypi:`pytest-codestyle`                         pytest plugin to run pycodestyle                                                                                                                                                                                                                                                                                                                                                        Mar 23, 2020    3 - Alpha              N/A
+   :pypi:`pytest-codspeed`                          Pytest plugin to create CodSpeed benchmarks                                                                                                                                                                                                                                                                                                                                             Jan 31, 2025    5 - Production/Stable  pytest>=3.8
+   :pypi:`pytest-collect-appoint-info`              set your encoding                                                                                                                                                                                                                                                                                                                                                                       Aug 03, 2023    N/A                    pytest
+   :pypi:`pytest-collect-formatter`                 Formatter for pytest collect output                                                                                                                                                                                                                                                                                                                                                     Mar 29, 2021    5 - Production/Stable  N/A
+   :pypi:`pytest-collect-formatter2`                Formatter for pytest collect output                                                                                                                                                                                                                                                                                                                                                     May 31, 2021    5 - Production/Stable  N/A
+   :pypi:`pytest-collect-interface-info-plugin`     Get executed interface information in pytest interface automation framework                                                                                                                                                                                                                                                                                                             Sep 25, 2023    4 - Beta               N/A
+   :pypi:`pytest-collector`                         Python package for collecting pytest.                                                                                                                                                                                                                                                                                                                                                   Aug 02, 2022    N/A                    pytest (>=7.0,<8.0)
+   :pypi:`pytest-collect-pytest-interinfo`          A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Sep 26, 2023    4 - Beta               N/A
+   :pypi:`pytest-colordots`                         Colorizes the progress indicators                                                                                                                                                                                                                                                                                                                                                       Oct 06, 2017    5 - Production/Stable  N/A
+   :pypi:`pytest-commander`                         An interactive GUI test runner for PyTest                                                                                                                                                                                                                                                                                                                                               Aug 17, 2021    N/A                    pytest (<7.0.0,>=6.2.4)
+   :pypi:`pytest-common-subject`                    pytest framework for testing different aspects of a common method                                                                                                                                                                                                                                                                                                                       Jun 12, 2024    N/A                    pytest<9,>=3.6
+   :pypi:`pytest-compare`                           pytest plugin for comparing call arguments.                                                                                                                                                                                                                                                                                                                                             Jun 22, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-concurrent`                        Concurrently execute test cases with multithread, multiprocess and gevent                                                                                                                                                                                                                                                                                                               Jan 12, 2019    4 - Beta               pytest (>=3.1.1)
+   :pypi:`pytest-config`                            Base configurations and utilities for developing    your Python project test suite with pytest.                                                                                                                                                                                                                                                                                         Nov 07, 2014    5 - Production/Stable  N/A
+   :pypi:`pytest-confluence-report`                 Package stands for pytest plugin to upload results into Confluence page.                                                                                                                                                                                                                                                                                                                Apr 17, 2022    N/A                    N/A
+   :pypi:`pytest-console-scripts`                   Pytest plugin for testing console scripts                                                                                                                                                                                                                                                                                                                                               May 31, 2023    4 - Beta               pytest (>=4.0.0)
+   :pypi:`pytest-consul`                            pytest plugin with fixtures for testing consul aware apps                                                                                                                                                                                                                                                                                                                               Nov 24, 2018    3 - Alpha              pytest
+   :pypi:`pytest-container`                         Pytest fixtures for writing container based tests                                                                                                                                                                                                                                                                                                                                       Dec 04, 2024    4 - Beta               pytest>=3.10
+   :pypi:`pytest-contextfixture`                    Define pytest fixtures as context managers.                                                                                                                                                                                                                                                                                                                                             Mar 12, 2013    4 - Beta               N/A
+   :pypi:`pytest-contexts`                          A plugin to run tests written with the Contexts framework using pytest                                                                                                                                                                                                                                                                                                                  May 19, 2021    4 - Beta               N/A
+   :pypi:`pytest-continuous`                        A pytest plugin to run tests continuously until failure or interruption.                                                                                                                                                                                                                                                                                                                Apr 23, 2024    N/A                    N/A
+   :pypi:`pytest-cookies`                           The pytest plugin for your Cookiecutter templates. 🍪                                                                                                                                                                                                                                                                                                                                   Mar 22, 2023    5 - Production/Stable  pytest (>=3.9.0)
+   :pypi:`pytest-copie`                             The pytest plugin for your copier templates 📒                                                                                                                                                                                                                                                                                                                                          Jan 31, 2025    3 - Alpha              pytest
+   :pypi:`pytest-copier`                            A pytest plugin to help testing Copier templates                                                                                                                                                                                                                                                                                                                                        Dec 11, 2023    4 - Beta               pytest>=7.3.2
+   :pypi:`pytest-couchdbkit`                        py.test extension for per-test couchdb databases using couchdbkit                                                                                                                                                                                                                                                                                                                       Apr 17, 2012    N/A                    N/A
+   :pypi:`pytest-count`                             count erros and send email                                                                                                                                                                                                                                                                                                                                                              Jan 12, 2018    4 - Beta               N/A
+   :pypi:`pytest-cov`                               Pytest plugin for measuring coverage.                                                                                                                                                                                                                                                                                                                                                   Apr 05, 2025    5 - Production/Stable  pytest>=4.6
+   :pypi:`pytest-cover`                             Pytest plugin for measuring coverage. Forked from \`pytest-cov\`.                                                                                                                                                                                                                                                                                                                       Aug 01, 2015    5 - Production/Stable  N/A
+   :pypi:`pytest-coverage`                                                                                                                                                                                                                                                                                                                                                                                                                  Jun 17, 2015    N/A                    N/A
+   :pypi:`pytest-coverage-context`                  Coverage dynamic context support for PyTest, including sub-processes                                                                                                                                                                                                                                                                                                                    Jun 28, 2023    4 - Beta               N/A
+   :pypi:`pytest-coveragemarkers`                   Using pytest markers to track functional coverage and filtering of tests                                                                                                                                                                                                                                                                                                                Oct 15, 2024    N/A                    pytest<8.0.0,>=7.1.2
+   :pypi:`pytest-cov-exclude`                       Pytest plugin for excluding tests based on coverage data                                                                                                                                                                                                                                                                                                                                Apr 29, 2016    4 - Beta               pytest (>=2.8.0,<2.9.0); extra == 'dev'
+   :pypi:`pytest_covid`                             Too many faillure, less tests.                                                                                                                                                                                                                                                                                                                                                          Jun 24, 2020    N/A                    N/A
+   :pypi:`pytest-cpp`                               Use pytest's runner to discover and execute C++ tests                                                                                                                                                                                                                                                                                                                                   Sep 18, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-cqase`                             Custom qase pytest plugin                                                                                                                                                                                                                                                                                                                                                               Aug 22, 2022    N/A                    pytest (>=7.1.2,<8.0.0)
+   :pypi:`pytest-cram`                              Run cram tests with pytest.                                                                                                                                                                                                                                                                                                                                                             Aug 08, 2020    N/A                    N/A
+   :pypi:`pytest-crate`                             Manages CrateDB instances during your integration tests                                                                                                                                                                                                                                                                                                                                 May 28, 2019    3 - Alpha              pytest (>=4.0)
+   :pypi:`pytest-cratedb`                           Manage CrateDB instances for integration tests                                                                                                                                                                                                                                                                                                                                          Oct 08, 2024    4 - Beta               pytest<9
+   :pypi:`pytest-cratedb-reporter`                  A pytest plugin for reporting test results to CrateDB                                                                                                                                                                                                                                                                                                                                   Mar 11, 2025    N/A                    pytest>=6.0.0
+   :pypi:`pytest-crayons`                           A pytest plugin for colorful print statements                                                                                                                                                                                                                                                                                                                                           Oct 08, 2023    N/A                    pytest
+   :pypi:`pytest-create`                            pytest-create                                                                                                                                                                                                                                                                                                                                                                           Feb 15, 2023    1 - Planning           N/A
+   :pypi:`pytest-cricri`                            A Cricri plugin for pytest.                                                                                                                                                                                                                                                                                                                                                             Jan 27, 2018    N/A                    pytest
+   :pypi:`pytest-crontab`                           add crontab task in crontab                                                                                                                                                                                                                                                                                                                                                             Dec 09, 2019    N/A                    N/A
+   :pypi:`pytest-csv`                               CSV output for pytest.                                                                                                                                                                                                                                                                                                                                                                  Apr 22, 2021    N/A                    pytest (>=6.0)
+   :pypi:`pytest-csv-params`                        Pytest plugin for Test Case Parametrization with CSV files                                                                                                                                                                                                                                                                                                                              Oct 25, 2024    5 - Production/Stable  pytest<9.0.0,>=8.3.0
+   :pypi:`pytest-curio`                             Pytest support for curio.                                                                                                                                                                                                                                                                                                                                                               Oct 06, 2024    N/A                    pytest
+   :pypi:`pytest-curl-report`                       pytest plugin to generate curl command line report                                                                                                                                                                                                                                                                                                                                      Dec 11, 2016    4 - Beta               N/A
+   :pypi:`pytest-custom-concurrency`                Custom grouping concurrence for pytest                                                                                                                                                                                                                                                                                                                                                  Feb 08, 2021    N/A                    N/A
+   :pypi:`pytest-custom-exit-code`                  Exit pytest test session with custom exit code in different scenarios                                                                                                                                                                                                                                                                                                                   Aug 07, 2019    4 - Beta               pytest (>=4.0.2)
+   :pypi:`pytest-custom-nodeid`                     Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report                                                                                                                                                                                                                                                                                   Mar 07, 2021    N/A                    N/A
+   :pypi:`pytest-custom-outputs`                    A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures.                                                                                                                                                                                                                              Jul 10, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-custom-report`                     Configure the symbols displayed for test outcomes                                                                                                                                                                                                                                                                                                                                       Jan 30, 2019    N/A                    pytest
+   :pypi:`pytest-custom-scheduling`                 Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report                                                                                                                                                                                                                                                                                   Mar 01, 2021    N/A                    N/A
+   :pypi:`pytest-custom-timeout`                    Use custom logic when a test times out. Based on pytest-timeout.                                                                                                                                                                                                                                                                                                                        Jan 08, 2025    4 - Beta               pytest>=8.0.0
+   :pypi:`pytest-cython`                            A plugin for testing Cython extension modules                                                                                                                                                                                                                                                                                                                                           Apr 05, 2024    5 - Production/Stable  pytest>=8
+   :pypi:`pytest-cython-collect`                                                                                                                                                                                                                                                                                                                                                                                                            Jun 17, 2022    N/A                    pytest
+   :pypi:`pytest-darker`                            A pytest plugin for checking of modified code using Darker                                                                                                                                                                                                                                                                                                                              Feb 25, 2024    N/A                    pytest <7,>=6.0.1
+   :pypi:`pytest-dash`                              pytest fixtures to run dash applications.                                                                                                                                                                                                                                                                                                                                               Mar 18, 2019    N/A                    N/A
+   :pypi:`pytest-dashboard`                                                                                                                                                                                                                                                                                                                                                                                                                 May 30, 2024    N/A                    pytest<8.0.0,>=7.4.3
+   :pypi:`pytest-data`                              Useful functions for managing data for pytest fixtures                                                                                                                                                                                                                                                                                                                                  Nov 01, 2016    5 - Production/Stable  N/A
+   :pypi:`pytest-databases`                         Reusable database fixtures for any and all databases.                                                                                                                                                                                                                                                                                                                                   Mar 23, 2025    4 - Beta               pytest
+   :pypi:`pytest-databricks`                        Pytest plugin for remote Databricks notebooks testing                                                                                                                                                                                                                                                                                                                                   Jul 29, 2020    N/A                    pytest
+   :pypi:`pytest-datadir`                           pytest plugin for test data directories and files                                                                                                                                                                                                                                                                                                                                       Feb 07, 2025    5 - Production/Stable  pytest>=7.0
+   :pypi:`pytest-datadir-mgr`                       Manager for test data: downloads, artifact caching, and a tmpdir context.                                                                                                                                                                                                                                                                                                               Apr 06, 2023    5 - Production/Stable  pytest (>=7.1)
+   :pypi:`pytest-datadir-ng`                        Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem.                                                                                                                                                                                                                                                                        Dec 25, 2019    5 - Production/Stable  pytest
+   :pypi:`pytest-datadir-nng`                       Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem.                                                                                                                                                                                                                                                                        Nov 09, 2022    5 - Production/Stable  pytest (>=7.0.0,<8.0.0)
+   :pypi:`pytest-data-extractor`                    A pytest plugin to extract relevant metadata about tests into an external file (currently only json support)                                                                                                                                                                                                                                                                            Jul 19, 2022    N/A                    pytest (>=7.0.1)
+   :pypi:`pytest-data-file`                         Fixture "data" and "case_data" for test from yaml file                                                                                                                                                                                                                                                                                                                                  Dec 04, 2019    N/A                    N/A
+   :pypi:`pytest-datafiles`                         py.test plugin to create a 'tmp_path' containing predefined files/directories.                                                                                                                                                                                                                                                                                                          Feb 24, 2023    5 - Production/Stable  pytest (>=3.6)
+   :pypi:`pytest-datafixtures`                      Data fixtures for pytest made simple                                                                                                                                                                                                                                                                                                                                                    Dec 05, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-data-from-files`                   pytest plugin to provide data from files loaded automatically                                                                                                                                                                                                                                                                                                                           Oct 13, 2021    4 - Beta               pytest
+   :pypi:`pytest-dataplugin`                        A pytest plugin for managing an archive of test data.                                                                                                                                                                                                                                                                                                                                   Sep 16, 2017    1 - Planning           N/A
+   :pypi:`pytest-datarecorder`                      A py.test plugin recording and comparing test output.                                                                                                                                                                                                                                                                                                                                   Jul 31, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-dataset`                           Plugin for loading different datasets for pytest by prefix from json or yaml files                                                                                                                                                                                                                                                                                                      Sep 01, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-data-suites`                       Class-based pytest parametrization                                                                                                                                                                                                                                                                                                                                                      Apr 06, 2024    N/A                    pytest<9.0,>=6.0
+   :pypi:`pytest-datatest`                          A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration).                                                                                                                                                                                                                                                                      Oct 15, 2020    4 - Beta               pytest (>=3.3)
+   :pypi:`pytest-db`                                Session scope fixture "db" for mysql query or change                                                                                                                                                                                                                                                                                                                                    Aug 22, 2024    N/A                    pytest
+   :pypi:`pytest-dbfixtures`                        Databases fixtures plugin for py.test.                                                                                                                                                                                                                                                                                                                                                  Dec 07, 2016    4 - Beta               N/A
+   :pypi:`pytest-db-plugin`                                                                                                                                                                                                                                                                                                                                                                                                                 Nov 27, 2021    N/A                    pytest (>=5.0)
+   :pypi:`pytest-dbt`                               Unit test dbt models with standard python tooling                                                                                                                                                                                                                                                                                                                                       Jun 08, 2023    2 - Pre-Alpha          pytest (>=7.0.0,<8.0.0)
+   :pypi:`pytest-dbt-adapter`                       A pytest plugin for testing dbt adapter plugins                                                                                                                                                                                                                                                                                                                                         Nov 24, 2021    N/A                    pytest (<7,>=6)
+   :pypi:`pytest-dbt-conventions`                   A pytest plugin for linting a dbt project's conventions                                                                                                                                                                                                                                                                                                                                 Mar 02, 2022    N/A                    pytest (>=6.2.5,<7.0.0)
+   :pypi:`pytest-dbt-core`                          Pytest extension for dbt.                                                                                                                                                                                                                                                                                                                                                               Jun 04, 2024    N/A                    pytest>=6.2.5; extra == "test"
+   :pypi:`pytest-dbt-duckdb`                        Fearless testing for dbt models, powered by DuckDB.                                                                                                                                                                                                                                                                                                                                     Feb 09, 2025    4 - Beta               pytest>=8.3.4
+   :pypi:`pytest-dbt-postgres`                      Pytest tooling to unittest DBT & Postgres models                                                                                                                                                                                                                                                                                                                                        Sep 03, 2024    N/A                    pytest<9.0.0,>=8.3.2
+   :pypi:`pytest-dbus-notification`                 D-BUS notifications for pytest results.                                                                                                                                                                                                                                                                                                                                                 Mar 05, 2014    5 - Production/Stable  N/A
+   :pypi:`pytest-dbx`                               Pytest plugin to run unit tests for dbx (Databricks CLI extensions) related code                                                                                                                                                                                                                                                                                                        Nov 29, 2022    N/A                    pytest (>=7.1.3,<8.0.0)
+   :pypi:`pytest-dc`                                Manages Docker containers during your integration tests                                                                                                                                                                                                                                                                                                                                 Aug 16, 2023    5 - Production/Stable  pytest >=3.3
+   :pypi:`pytest-deadfixtures`                      A simple plugin to list unused fixtures in pytest                                                                                                                                                                                                                                                                                                                                       Jul 23, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-deduplicate`                       Identifies duplicate unit tests                                                                                                                                                                                                                                                                                                                                                         Aug 12, 2023    4 - Beta               pytest
+   :pypi:`pytest-deepcov`                           deepcov                                                                                                                                                                                                                                                                                                                                                                                 Mar 30, 2021    N/A                    N/A
+   :pypi:`pytest_defer`                             A 'defer' fixture for pytest                                                                                                                                                                                                                                                                                                                                                            Nov 13, 2024    N/A                    pytest>=8.3
+   :pypi:`pytest-demo-plugin`                       pytest示例插件                                                                                                                                                                                                                                                                                                                                                                          May 15, 2021    N/A                    N/A
+   :pypi:`pytest-dependency`                        Manage dependencies of tests                                                                                                                                                                                                                                                                                                                                                            Dec 31, 2023    4 - Beta               N/A
+   :pypi:`pytest-depends`                           Tests that depend on other tests                                                                                                                                                                                                                                                                                                                                                        Apr 05, 2020    5 - Production/Stable  pytest (>=3)
+   :pypi:`pytest-deprecate`                         Mark tests as testing a deprecated feature with a warning note.                                                                                                                                                                                                                                                                                                                         Jul 01, 2019    N/A                    N/A
+   :pypi:`pytest-deprecator`                        A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Dec 02, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-describe`                          Describe-style plugin for pytest                                                                                                                                                                                                                                                                                                                                                        Feb 10, 2024    5 - Production/Stable  pytest <9,>=4.6
+   :pypi:`pytest-describe-it`                       plugin for rich text descriptions                                                                                                                                                                                                                                                                                                                                                       Jul 19, 2019    4 - Beta               pytest
+   :pypi:`pytest-deselect-if`                       A plugin to deselect pytests tests rather than using skipif                                                                                                                                                                                                                                                                                                                             Dec 26, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-devpi-server`                      DevPI server fixture for py.test                                                                                                                                                                                                                                                                                                                                                        Oct 17, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-dhos`                              Common fixtures for pytest in DHOS services and libraries                                                                                                                                                                                                                                                                                                                               Sep 07, 2022    N/A                    N/A
+   :pypi:`pytest-diamond`                           pytest plugin for diamond                                                                                                                                                                                                                                                                                                                                                               Aug 31, 2015    4 - Beta               N/A
+   :pypi:`pytest-dicom`                             pytest plugin to provide DICOM fixtures                                                                                                                                                                                                                                                                                                                                                 Dec 19, 2018    3 - Alpha              pytest
+   :pypi:`pytest-dictsdiff`                                                                                                                                                                                                                                                                                                                                                                                                                 Jul 26, 2019    N/A                    N/A
+   :pypi:`pytest-diff`                              A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Mar 30, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-diffeo`                            A package to prevent Dependency Confusion attacks against Yandex.                                                                                                                                                                                                                                                                                                                       Feb 20, 2024    N/A                    N/A
+   :pypi:`pytest-diff-selector`                     Get tests affected by code changes (using git)                                                                                                                                                                                                                                                                                                                                          Feb 24, 2022    4 - Beta               pytest (>=6.2.2) ; extra == 'all'
+   :pypi:`pytest-difido`                            PyTest plugin for generating Difido reports                                                                                                                                                                                                                                                                                                                                             Oct 23, 2022    4 - Beta               pytest (>=4.0.0)
+   :pypi:`pytest-dir-equal`                         pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing                                                                                                                                                                                                                                                                           Dec 11, 2023    4 - Beta               pytest>=7.3.2
+   :pypi:`pytest-dirty`                             Static import analysis for thrifty testing.                                                                                                                                                                                                                                                                                                                                             Jul 11, 2024    3 - Alpha              pytest>=8.2; extra == "dev"
+   :pypi:`pytest-disable`                           pytest plugin to disable a test and skip it from testrun                                                                                                                                                                                                                                                                                                                                Sep 10, 2015    4 - Beta               N/A
+   :pypi:`pytest-disable-plugin`                    Disable plugins per test                                                                                                                                                                                                                                                                                                                                                                Feb 28, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-discord`                           A pytest plugin to notify test results to a Discord channel.                                                                                                                                                                                                                                                                                                                            May 11, 2024    4 - Beta               pytest!=6.0.0,<9,>=3.3.2
+   :pypi:`pytest-discover`                          Pytest plugin to record discovered tests in a file                                                                                                                                                                                                                                                                                                                                      Mar 26, 2024    N/A                    pytest
+   :pypi:`pytest-ditto`                             Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats.                                                                                                                                                                                                                                                                                                  Jun 09, 2024    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-ditto-pandas`                      pytest-ditto plugin for pandas snapshots.                                                                                                                                                                                                                                                                                                                                               May 29, 2024    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-ditto-pyarrow`                     pytest-ditto plugin for pyarrow tables.                                                                                                                                                                                                                                                                                                                                                 Jun 09, 2024    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-django`                            A Django plugin for pytest.                                                                                                                                                                                                                                                                                                                                                             Apr 03, 2025    5 - Production/Stable  pytest>=7.0.0
+   :pypi:`pytest-django-ahead`                      A Django plugin for pytest.                                                                                                                                                                                                                                                                                                                                                             Oct 27, 2016    5 - Production/Stable  pytest (>=2.9)
+   :pypi:`pytest-djangoapp`                         Nice pytest plugin to help you with Django pluggable application testing.                                                                                                                                                                                                                                                                                                               May 19, 2023    4 - Beta               pytest
+   :pypi:`pytest-django-cache-xdist`                A djangocachexdist plugin for pytest                                                                                                                                                                                                                                                                                                                                                    May 12, 2020    4 - Beta               N/A
+   :pypi:`pytest-django-casperjs`                   Integrate CasperJS with your django tests as a pytest fixture.                                                                                                                                                                                                                                                                                                                          Mar 15, 2015    2 - Pre-Alpha          N/A
+   :pypi:`pytest-django-class`                      A pytest plugin for running django in class-scoped fixtures                                                                                                                                                                                                                                                                                                                             Aug 08, 2023    4 - Beta               N/A
+   :pypi:`pytest-django-docker-pg`                                                                                                                                                                                                                                                                                                                                                                                                          Jun 13, 2024    5 - Production/Stable  pytest<9.0.0,>=7.0.0
+   :pypi:`pytest-django-dotenv`                     Pytest plugin used to setup environment variables with django-dotenv                                                                                                                                                                                                                                                                                                                    Nov 26, 2019    4 - Beta               pytest (>=2.6.0)
+   :pypi:`pytest-django-factories`                  Factories for your Django models that can be used as Pytest fixtures.                                                                                                                                                                                                                                                                                                                   Nov 12, 2020    4 - Beta               N/A
+   :pypi:`pytest-django-filefield`                  Replaces FileField.storage with something you can patch globally.                                                                                                                                                                                                                                                                                                                       May 09, 2022    5 - Production/Stable  pytest >= 5.2
+   :pypi:`pytest-django-gcir`                       A Django plugin for pytest.                                                                                                                                                                                                                                                                                                                                                             Mar 06, 2018    5 - Production/Stable  N/A
+   :pypi:`pytest-django-haystack`                   Cleanup your Haystack indexes between tests                                                                                                                                                                                                                                                                                                                                             Sep 03, 2017    5 - Production/Stable  pytest (>=2.3.4)
+   :pypi:`pytest-django-ifactory`                   A model instance factory for pytest-django                                                                                                                                                                                                                                                                                                                                              Aug 27, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-django-lite`                       The bare minimum to integrate py.test with Django.                                                                                                                                                                                                                                                                                                                                      Jan 30, 2014    N/A                    N/A
+   :pypi:`pytest-django-liveserver-ssl`                                                                                                                                                                                                                                                                                                                                                                                                     Jan 09, 2025    3 - Alpha              N/A
+   :pypi:`pytest-django-model`                      A Simple Way to Test your Django Models                                                                                                                                                                                                                                                                                                                                                 Feb 14, 2019    4 - Beta               N/A
+   :pypi:`pytest-django-ordering`                   A pytest plugin for preserving the order in which Django runs tests.                                                                                                                                                                                                                                                                                                                    Jul 25, 2019    5 - Production/Stable  pytest (>=2.3.0)
+   :pypi:`pytest-django-queries`                    Generate performance reports from your django database performance tests.                                                                                                                                                                                                                                                                                                               Mar 01, 2021    N/A                    N/A
+   :pypi:`pytest-djangorestframework`               A djangorestframework plugin for pytest                                                                                                                                                                                                                                                                                                                                                 Aug 11, 2019    4 - Beta               N/A
+   :pypi:`pytest-django-rq`                         A pytest plugin to help writing unit test for django-rq                                                                                                                                                                                                                                                                                                                                 Apr 13, 2020    4 - Beta               N/A
+   :pypi:`pytest-django-sqlcounts`                  py.test plugin for reporting the number of SQLs executed per django testcase.                                                                                                                                                                                                                                                                                                           Jun 16, 2015    4 - Beta               N/A
+   :pypi:`pytest-django-testing-postgresql`         Use a temporary PostgreSQL database with pytest-django                                                                                                                                                                                                                                                                                                                                  Jan 31, 2022    4 - Beta               N/A
+   :pypi:`pytest-doc`                               A documentation plugin for py.test.                                                                                                                                                                                                                                                                                                                                                     Jun 28, 2015    5 - Production/Stable  N/A
+   :pypi:`pytest-docfiles`                          pytest plugin to test codeblocks in your documentation.                                                                                                                                                                                                                                                                                                                                 Dec 22, 2021    4 - Beta               pytest (>=3.7.0)
+   :pypi:`pytest-docgen`                            An RST Documentation Generator for pytest-based test suites                                                                                                                                                                                                                                                                                                                             Apr 17, 2020    N/A                    N/A
+   :pypi:`pytest-docker`                            Simple pytest fixtures for Docker and Docker Compose based tests                                                                                                                                                                                                                                                                                                                        Feb 06, 2025    N/A                    pytest<9.0,>=4.0
+   :pypi:`pytest-docker-apache-fixtures`            Pytest fixtures for testing with apache2 (httpd).                                                                                                                                                                                                                                                                                                                                       Aug 12, 2024    4 - Beta               pytest
+   :pypi:`pytest-docker-butla`                                                                                                                                                                                                                                                                                                                                                                                                              Jun 16, 2019    3 - Alpha              N/A
+   :pypi:`pytest-dockerc`                           Run, manage and stop Docker Compose project from Docker API                                                                                                                                                                                                                                                                                                                             Oct 09, 2020    5 - Production/Stable  pytest (>=3.0)
+   :pypi:`pytest-docker-compose`                    Manages Docker containers during your integration tests                                                                                                                                                                                                                                                                                                                                 Jan 26, 2021    5 - Production/Stable  pytest (>=3.3)
+   :pypi:`pytest-docker-compose-v2`                 Manages Docker containers during your integration tests                                                                                                                                                                                                                                                                                                                                 Dec 11, 2024    4 - Beta               pytest>=7.2.2
+   :pypi:`pytest-docker-db`                         A plugin to use docker databases for pytests                                                                                                                                                                                                                                                                                                                                            Mar 20, 2021    5 - Production/Stable  pytest (>=3.1.1)
+   :pypi:`pytest-docker-fixtures`                   pytest docker fixtures                                                                                                                                                                                                                                                                                                                                                                  Apr 03, 2024    3 - Alpha              N/A
+   :pypi:`pytest-docker-git-fixtures`               Pytest fixtures for testing with git scm.                                                                                                                                                                                                                                                                                                                                               Aug 12, 2024    4 - Beta               pytest
+   :pypi:`pytest-docker-haproxy-fixtures`           Pytest fixtures for testing with haproxy.                                                                                                                                                                                                                                                                                                                                               Aug 12, 2024    4 - Beta               pytest
+   :pypi:`pytest-docker-pexpect`                    pytest plugin for writing functional tests with pexpect and docker                                                                                                                                                                                                                                                                                                                      Jan 14, 2019    N/A                    pytest
+   :pypi:`pytest-docker-postgresql`                 A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Sep 24, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-docker-py`                         Easy to use, simple to extend, pytest plugin that minimally leverages docker-py.                                                                                                                                                                                                                                                                                                        Nov 27, 2018    N/A                    pytest (==4.0.0)
+   :pypi:`pytest-docker-registry-fixtures`          Pytest fixtures for testing with docker registries.                                                                                                                                                                                                                                                                                                                                     Aug 12, 2024    4 - Beta               pytest
+   :pypi:`pytest-docker-service`                    pytest plugin to start docker container                                                                                                                                                                                                                                                                                                                                                 Jan 03, 2024    3 - Alpha              pytest (>=7.1.3)
+   :pypi:`pytest-docker-squid-fixtures`             Pytest fixtures for testing with squid.                                                                                                                                                                                                                                                                                                                                                 Aug 12, 2024    4 - Beta               pytest
+   :pypi:`pytest-docker-tools`                      Docker integration tests for pytest                                                                                                                                                                                                                                                                                                                                                     Mar 16, 2025    4 - Beta               pytest>=6.0.1
+   :pypi:`pytest-docs`                              Documentation tool for pytest                                                                                                                                                                                                                                                                                                                                                           Nov 11, 2018    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-docstyle`                          pytest plugin to run pydocstyle                                                                                                                                                                                                                                                                                                                                                         Mar 23, 2020    3 - Alpha              N/A
+   :pypi:`pytest-doctest-custom`                    A py.test plugin for customizing string representations of doctest results.                                                                                                                                                                                                                                                                                                             Jul 25, 2016    4 - Beta               N/A
+   :pypi:`pytest-doctest-ellipsis-markers`          Setup additional values for ELLIPSIS_MARKER for doctests                                                                                                                                                                                                                                                                                                                                Jan 12, 2018    4 - Beta               N/A
+   :pypi:`pytest-doctest-import`                    A simple pytest plugin to import names and add them to the doctest namespace.                                                                                                                                                                                                                                                                                                           Nov 13, 2018    4 - Beta               pytest (>=3.3.0)
+   :pypi:`pytest-doctest-mkdocstrings`              Run pytest --doctest-modules with markdown docstrings in code blocks (\`\`\`)                                                                                                                                                                                                                                                                                                           Mar 02, 2024    N/A                    pytest
+   :pypi:`pytest-doctestplus`                       Pytest plugin with advanced doctest features.                                                                                                                                                                                                                                                                                                                                           Jan 25, 2025    5 - Production/Stable  pytest>=4.6
+   :pypi:`pytest-documentary`                       A simple pytest plugin to generate test documentation                                                                                                                                                                                                                                                                                                                                   Jul 11, 2024    N/A                    pytest
+   :pypi:`pytest-dogu-report`                       pytest plugin for dogu report                                                                                                                                                                                                                                                                                                                                                           Jul 07, 2023    N/A                    N/A
+   :pypi:`pytest-dogu-sdk`                          pytest plugin for the Dogu                                                                                                                                                                                                                                                                                                                                                              Dec 14, 2023    N/A                    N/A
+   :pypi:`pytest-dolphin`                           Some extra stuff that we use ininternally                                                                                                                                                                                                                                                                                                                                               Nov 30, 2016    4 - Beta               pytest (==3.0.4)
+   :pypi:`pytest-donde`                             record pytest session characteristics per test item (coverage and duration) into a persistent file and use them in your own plugin or script.                                                                                                                                                                                                                                           Oct 01, 2023    4 - Beta               pytest >=7.3.1
+   :pypi:`pytest-doorstop`                          A pytest plugin for adding test results into doorstop items.                                                                                                                                                                                                                                                                                                                            Jun 09, 2020    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-dotenv`                            A py.test plugin that parses environment files before running tests                                                                                                                                                                                                                                                                                                                     Jun 16, 2020    4 - Beta               pytest (>=5.0.0)
+   :pypi:`pytest-dot-only-pkcopley`                 A Pytest marker for only running a single test                                                                                                                                                                                                                                                                                                                                          Oct 27, 2023    N/A                    N/A
+   :pypi:`pytest-dparam`                            A more readable alternative to @pytest.mark.parametrize.                                                                                                                                                                                                                                                                                                                                Aug 27, 2024    6 - Mature             pytest
+   :pypi:`pytest-dpg`                               pytest-dpg is a pytest plugin for testing Dear PyGui (DPG) applications                                                                                                                                                                                                                                                                                                                 Aug 13, 2024    N/A                    N/A
+   :pypi:`pytest-draw`                              Pytest plugin for randomly selecting a specific number of tests                                                                                                                                                                                                                                                                                                                         Mar 21, 2023    3 - Alpha              pytest
+   :pypi:`pytest-drf`                               A Django REST framework plugin for pytest.                                                                                                                                                                                                                                                                                                                                              Jul 12, 2022    5 - Production/Stable  pytest (>=3.7)
+   :pypi:`pytest-drivings`                          Tool to allow webdriver automation to be ran locally or remotely                                                                                                                                                                                                                                                                                                                        Jan 13, 2021    N/A                    N/A
+   :pypi:`pytest-drop-dup-tests`                    A Pytest plugin to drop duplicated tests during collection                                                                                                                                                                                                                                                                                                                              Mar 04, 2024    5 - Production/Stable  pytest >=7
+   :pypi:`pytest-dryci`                             Test caching plugin for pytest                                                                                                                                                                                                                                                                                                                                                          Sep 27, 2024    4 - Beta               N/A
+   :pypi:`pytest-dryrun`                            A Pytest plugin to ignore tests during collection without reporting them in the test summary.                                                                                                                                                                                                                                                                                           Jan 19, 2025    5 - Production/Stable  pytest<9,>=7.40
+   :pypi:`pytest-dummynet`                          A py.test plugin providing access to a dummynet.                                                                                                                                                                                                                                                                                                                                        Dec 15, 2021    5 - Production/Stable  pytest
+   :pypi:`pytest-dump2json`                         A pytest plugin for dumping test results to json.                                                                                                                                                                                                                                                                                                                                       Jun 29, 2015    N/A                    N/A
+   :pypi:`pytest-duration-insights`                                                                                                                                                                                                                                                                                                                                                                                                         Jul 15, 2024    N/A                    N/A
+   :pypi:`pytest-durations`                         Pytest plugin reporting fixtures and test functions execution time.                                                                                                                                                                                                                                                                                                                     Mar 18, 2025    5 - Production/Stable  pytest>=4.6
+   :pypi:`pytest-dynamicrerun`                      A pytest plugin to rerun tests dynamically based off of test outcome and output.                                                                                                                                                                                                                                                                                                        Aug 15, 2020    4 - Beta               N/A
+   :pypi:`pytest-dynamodb`                          DynamoDB fixtures for pytest                                                                                                                                                                                                                                                                                                                                                            Apr 04, 2025    5 - Production/Stable  pytest
+   :pypi:`pytest-easy-addoption`                    pytest-easy-addoption: Easy way to work with pytest addoption                                                                                                                                                                                                                                                                                                                           Jan 22, 2020    N/A                    N/A
+   :pypi:`pytest-easy-api`                          A package to prevent Dependency Confusion attacks against Yandex.                                                                                                                                                                                                                                                                                                                       Feb 16, 2024    N/A                    N/A
+   :pypi:`pytest-easyMPI`                           Package that supports mpi tests in pytest                                                                                                                                                                                                                                                                                                                                               Oct 21, 2020    N/A                    N/A
+   :pypi:`pytest-easyread`                          pytest plugin that makes terminal printouts of the reports easier to read                                                                                                                                                                                                                                                                                                               Nov 17, 2017    N/A                    N/A
+   :pypi:`pytest-easy-server`                       Pytest plugin for easy testing against servers                                                                                                                                                                                                                                                                                                                                          May 01, 2021    4 - Beta               pytest (<5.0.0,>=4.3.1) ; python_version < "3.5"
+   :pypi:`pytest-ebics-sandbox`                     A pytest plugin for testing against an EBICS sandbox server. Requires docker.                                                                                                                                                                                                                                                                                                           Aug 15, 2022    N/A                    N/A
+   :pypi:`pytest-ec2`                               Pytest execution on EC2 instance                                                                                                                                                                                                                                                                                                                                                        Oct 22, 2019    3 - Alpha              N/A
+   :pypi:`pytest-echo`                              pytest plugin that allows to dump environment variables, package version and generic attributes                                                                                                                                                                                                                                                                                         Apr 01, 2025    5 - Production/Stable  pytest>=8.3.3
+   :pypi:`pytest-edit`                              Edit the source code of a failed test with \`pytest --edit\`.                                                                                                                                                                                                                                                                                                                           Nov 17, 2024    N/A                    pytest
+   :pypi:`pytest-ekstazi`                           Pytest plugin to select test using Ekstazi algorithm                                                                                                                                                                                                                                                                                                                                    Sep 10, 2022    N/A                    pytest
+   :pypi:`pytest-elasticsearch`                     Elasticsearch fixtures and fixture factories for Pytest.                                                                                                                                                                                                                                                                                                                                Dec 03, 2024    5 - Production/Stable  pytest>=7.0
+   :pypi:`pytest-elasticsearch-test`                Elasticsearch fixtures and fixture factories for Pytest.                                                                                                                                                                                                                                                                                                                                Aug 21, 2024    5 - Production/Stable  pytest>=7.0
+   :pypi:`pytest-elements`                          Tool to help automate user interfaces                                                                                                                                                                                                                                                                                                                                                   Jan 13, 2021    N/A                    pytest (>=5.4,<6.0)
+   :pypi:`pytest-eliot`                             An eliot plugin for pytest.                                                                                                                                                                                                                                                                                                                                                             Aug 31, 2022    1 - Planning           pytest (>=5.4.0)
+   :pypi:`pytest-elk-reporter`                      A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Jul 25, 2024    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-email`                             Send execution result email                                                                                                                                                                                                                                                                                                                                                             Jul 08, 2020    N/A                    pytest
+   :pypi:`pytest-embedded`                          A pytest plugin that designed for embedded testing.                                                                                                                                                                                                                                                                                                                                     Mar 13, 2025    5 - Production/Stable  pytest>=7.0
+   :pypi:`pytest-embedded-arduino`                  Make pytest-embedded plugin work with Arduino.                                                                                                                                                                                                                                                                                                                                          Mar 13, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-embedded-idf`                      Make pytest-embedded plugin work with ESP-IDF.                                                                                                                                                                                                                                                                                                                                          Mar 13, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-embedded-jtag`                     Make pytest-embedded plugin work with JTAG.                                                                                                                                                                                                                                                                                                                                             Mar 13, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-embedded-nuttx`                    Make pytest-embedded plugin work with NuttX.                                                                                                                                                                                                                                                                                                                                            Mar 13, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-embedded-qemu`                     Make pytest-embedded plugin work with QEMU.                                                                                                                                                                                                                                                                                                                                             Mar 13, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-embedded-serial`                   Make pytest-embedded plugin work with Serial.                                                                                                                                                                                                                                                                                                                                           Mar 13, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-embedded-serial-esp`               Make pytest-embedded plugin work with Espressif target boards.                                                                                                                                                                                                                                                                                                                          Mar 13, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-embedded-wokwi`                    Make pytest-embedded plugin work with the Wokwi CLI.                                                                                                                                                                                                                                                                                                                                    Mar 13, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-embrace`                           💝  Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate.                                                                                                                                                                                                                                                                                          Mar 25, 2023    N/A                    pytest (>=7.0,<8.0)
+   :pypi:`pytest-emoji`                             A pytest plugin that adds emojis to your test result report                                                                                                                                                                                                                                                                                                                             Feb 19, 2019    4 - Beta               pytest (>=4.2.1)
+   :pypi:`pytest-emoji-output`                      Pytest plugin to represent test output with emoji support                                                                                                                                                                                                                                                                                                                               Apr 09, 2023    4 - Beta               pytest (==7.0.1)
+   :pypi:`pytest-enabler`                           Enable installed pytest plugins                                                                                                                                                                                                                                                                                                                                                         Sep 12, 2024    5 - Production/Stable  pytest!=8.1.*,>=6; extra == "test"
+   :pypi:`pytest-encode`                            set your encoding and logger                                                                                                                                                                                                                                                                                                                                                            Nov 06, 2021    N/A                    N/A
+   :pypi:`pytest-encode-kane`                       set your encoding and logger                                                                                                                                                                                                                                                                                                                                                            Nov 16, 2021    N/A                    pytest
+   :pypi:`pytest-encoding`                          set your encoding and logger                                                                                                                                                                                                                                                                                                                                                            Aug 11, 2023    N/A                    pytest
+   :pypi:`pytest_energy_reporter`                   An energy estimation reporter for pytest                                                                                                                                                                                                                                                                                                                                                Mar 28, 2024    3 - Alpha              pytest<9.0.0,>=8.1.1
+   :pypi:`pytest-enhanced-reports`                  Enhanced test reports for pytest                                                                                                                                                                                                                                                                                                                                                        Dec 15, 2022    N/A                    N/A
+   :pypi:`pytest-enhancements`                      Improvements for pytest (rejected upstream)                                                                                                                                                                                                                                                                                                                                             Oct 30, 2019    4 - Beta               N/A
+   :pypi:`pytest-env`                               pytest plugin that allows you to add environment variables.                                                                                                                                                                                                                                                                                                                             Sep 17, 2024    5 - Production/Stable  pytest>=8.3.3
+   :pypi:`pytest-envfiles`                          A py.test plugin that parses environment files before running tests                                                                                                                                                                                                                                                                                                                     Oct 08, 2015    3 - Alpha              N/A
+   :pypi:`pytest-env-info`                          Push information about the running pytest into envvars                                                                                                                                                                                                                                                                                                                                  Nov 25, 2017    4 - Beta               pytest (>=3.1.1)
+   :pypi:`pytest-environment`                       Pytest Environment                                                                                                                                                                                                                                                                                                                                                                      Mar 17, 2024    1 - Planning           N/A
+   :pypi:`pytest-envraw`                            py.test plugin that allows you to add environment variables.                                                                                                                                                                                                                                                                                                                            Aug 27, 2020    4 - Beta               pytest (>=2.6.0)
+   :pypi:`pytest-envvars`                           Pytest plugin to validate use of envvars on your tests                                                                                                                                                                                                                                                                                                                                  Jun 13, 2020    5 - Production/Stable  pytest (>=3.0.0)
+   :pypi:`pytest-env-yaml`                                                                                                                                                                                                                                                                                                                                                                                                                  Apr 02, 2019    N/A                    N/A
+   :pypi:`pytest-eradicate`                         pytest plugin to check for commented out code                                                                                                                                                                                                                                                                                                                                           Sep 08, 2020    N/A                    pytest (>=2.4.2)
+   :pypi:`pytest_erp`                               py.test plugin to send test info to report portal dynamically                                                                                                                                                                                                                                                                                                                           Jan 13, 2015    N/A                    N/A
+   :pypi:`pytest-error-for-skips`                   Pytest plugin to treat skipped tests a test failure                                                                                                                                                                                                                                                                                                                                     Dec 19, 2019    4 - Beta               pytest (>=4.6)
+   :pypi:`pytest-errxfail`                          pytest plugin to mark a test as xfailed if it fails with the specified error message in the captured output                                                                                                                                                                                                                                                                             Jan 06, 2025    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-eth`                               PyTest plugin for testing Smart Contracts for Ethereum Virtual Machine (EVM).                                                                                                                                                                                                                                                                                                           Aug 14, 2020    1 - Planning           N/A
+   :pypi:`pytest-ethereum`                          pytest-ethereum: Pytest library for ethereum projects.                                                                                                                                                                                                                                                                                                                                  Jun 24, 2019    3 - Alpha              pytest (==3.3.2); extra == 'dev'
+   :pypi:`pytest-eucalyptus`                        Pytest Plugin for BDD                                                                                                                                                                                                                                                                                                                                                                   Jun 28, 2022    N/A                    pytest (>=4.2.0)
+   :pypi:`pytest-evals`                             A pytest plugin for running and analyzing LLM evaluation tests                                                                                                                                                                                                                                                                                                                          Feb 02, 2025    N/A                    pytest>=7.0.0
+   :pypi:`pytest-eventlet`                          Applies eventlet monkey-patch as a pytest plugin.                                                                                                                                                                                                                                                                                                                                       Oct 04, 2021    N/A                    pytest ; extra == 'dev'
+   :pypi:`pytest_evm`                               The testing package containing tools to test Web3-based projects                                                                                                                                                                                                                                                                                                                        Sep 23, 2024    4 - Beta               pytest<9.0.0,>=8.1.1
+   :pypi:`pytest_exact_fixtures`                    Parse queries in Lucene and Elasticsearch syntaxes                                                                                                                                                                                                                                                                                                                                      Feb 04, 2019    N/A                    N/A
+   :pypi:`pytest-examples`                          Pytest plugin for testing examples in docstrings and markdown files.                                                                                                                                                                                                                                                                                                                    Mar 23, 2025    N/A                    pytest>=7
+   :pypi:`pytest-exasol-backend`                                                                                                                                                                                                                                                                                                                                                                                                            Feb 11, 2025    N/A                    pytest<9,>=7
+   :pypi:`pytest-exasol-extension`                                                                                                                                                                                                                                                                                                                                                                                                          Feb 11, 2025    N/A                    pytest<9,>=7
+   :pypi:`pytest-exasol-itde`                                                                                                                                                                                                                                                                                                                                                                                                               Nov 22, 2024    N/A                    pytest<9,>=7
+   :pypi:`pytest-exasol-saas`                                                                                                                                                                                                                                                                                                                                                                                                               Nov 22, 2024    N/A                    pytest<9,>=7
+   :pypi:`pytest-exasol-slc`                                                                                                                                                                                                                                                                                                                                                                                                                Feb 11, 2025    N/A                    pytest<9,>=7
+   :pypi:`pytest-excel`                             pytest plugin for generating excel reports                                                                                                                                                                                                                                                                                                                                              Jun 18, 2024    5 - Production/Stable  pytest>3.6
+   :pypi:`pytest-exceptional`                       Better exceptions                                                                                                                                                                                                                                                                                                                                                                       Mar 16, 2017    4 - Beta               N/A
+   :pypi:`pytest-exception-script`                  Walk your code through exception script to check it's resiliency to failures.                                                                                                                                                                                                                                                                                                           Aug 04, 2020    3 - Alpha              pytest
+   :pypi:`pytest-executable`                        pytest plugin for testing executables                                                                                                                                                                                                                                                                                                                                                   Oct 07, 2023    N/A                    pytest <8,>=5
+   :pypi:`pytest-execution-timer`                   A timer for the phases of Pytest's execution.                                                                                                                                                                                                                                                                                                                                           Dec 24, 2021    4 - Beta               N/A
+   :pypi:`pytest-exit-code`                         A pytest plugin that overrides the built-in exit codes to retain more information about the test results.                                                                                                                                                                                                                                                                               May 06, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-exit-status`                       Enhance.                                                                                                                                                                                                                                                                                                                                                                                Jan 25, 2025    N/A                    pytest>=8.0.0
+   :pypi:`pytest-expect`                            py.test plugin to store test expectations and mark tests based on them                                                                                                                                                                                                                                                                                                                  Apr 21, 2016    4 - Beta               N/A
+   :pypi:`pytest-expectdir`                         A pytest plugin to provide initial/expected directories, and check a test transforms the initial directory to the expected one                                                                                                                                                                                                                                                          Mar 19, 2023    5 - Production/Stable  pytest (>=5.0)
+   :pypi:`pytest-expected`                          Record and play back your expectations                                                                                                                                                                                                                                                                                                                                                  Feb 26, 2025    N/A                    pytest
+   :pypi:`pytest-expecter`                          Better testing with expecter and pytest.                                                                                                                                                                                                                                                                                                                                                Sep 18, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-expectr`                           This plugin is used to expect multiple assert using pytest framework.                                                                                                                                                                                                                                                                                                                   Oct 05, 2018    N/A                    pytest (>=2.4.2)
+   :pypi:`pytest-expect-test`                       A fixture to support expect tests in pytest                                                                                                                                                                                                                                                                                                                                             Apr 10, 2023    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-experiments`                       A pytest plugin to help developers of research-oriented software projects keep track of the results of their numerical experiments.                                                                                                                                                                                                                                                     Dec 13, 2021    4 - Beta               pytest (>=6.2.5,<7.0.0)
+   :pypi:`pytest-explicit`                          A Pytest plugin to ignore certain marked tests by default                                                                                                                                                                                                                                                                                                                               Jun 15, 2021    5 - Production/Stable  pytest
+   :pypi:`pytest-exploratory`                       Interactive console for pytest.                                                                                                                                                                                                                                                                                                                                                         Sep 18, 2024    N/A                    pytest>=6.2
+   :pypi:`pytest-explorer`                          terminal ui for exploring and running tests                                                                                                                                                                                                                                                                                                                                             Aug 01, 2023    N/A                    N/A
+   :pypi:`pytest-ext`                               pytest plugin for automation test                                                                                                                                                                                                                                                                                                                                                       Mar 31, 2024    N/A                    pytest>=5.3
+   :pypi:`pytest-extended-mock`                     a pytest extension for easy mock setup                                                                                                                                                                                                                                                                                                                                                  Mar 12, 2025    N/A                    pytest<9.0.0,>=8.3.5
+   :pypi:`pytest-extensions`                        A collection of helpers for pytest to ease testing                                                                                                                                                                                                                                                                                                                                      Aug 17, 2022    4 - Beta               pytest ; extra == 'testing'
+   :pypi:`pytest-external-blockers`                 a special outcome for tests that are blocked for external reasons                                                                                                                                                                                                                                                                                                                       Oct 05, 2021    N/A                    pytest
+   :pypi:`pytest_extra`                             Some helpers for writing tests with pytest.                                                                                                                                                                                                                                                                                                                                             Aug 14, 2014    N/A                    N/A
+   :pypi:`pytest-extra-durations`                   A pytest plugin to get durations on a per-function basis and per module basis.                                                                                                                                                                                                                                                                                                          Apr 21, 2020    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-extra-markers`                     Additional pytest markers to dynamically enable/disable tests viia CLI flags                                                                                                                                                                                                                                                                                                            Mar 05, 2023    4 - Beta               pytest
+   :pypi:`pytest-f3ts`                              Pytest Plugin for communicating test results and information to a FixturFab Test Runner GUI                                                                                                                                                                                                                                                                                             Feb 21, 2025    N/A                    pytest<8.0.0,>=7.2.1
+   :pypi:`pytest-fabric`                            Provides test utilities to run fabric task tests by using docker containers                                                                                                                                                                                                                                                                                                             Sep 12, 2018    5 - Production/Stable  N/A
+   :pypi:`pytest-factor`                            A package to prevent Dependency Confusion attacks against Yandex.                                                                                                                                                                                                                                                                                                                       Feb 20, 2024    N/A                    N/A
+   :pypi:`pytest-factory`                           Use factories for test setup with py.test                                                                                                                                                                                                                                                                                                                                               Sep 06, 2020    3 - Alpha              pytest (>4.3)
+   :pypi:`pytest-factoryboy`                        Factory Boy support for pytest.                                                                                                                                                                                                                                                                                                                                                         Mar 05, 2024    6 - Mature             pytest (>=6.2)
+   :pypi:`pytest-factoryboy-fixtures`               Generates pytest fixtures that allow the use of type hinting                                                                                                                                                                                                                                                                                                                            Jun 25, 2020    N/A                    N/A
+   :pypi:`pytest-factoryboy-state`                  Simple factoryboy random state management                                                                                                                                                                                                                                                                                                                                               Mar 22, 2022    5 - Production/Stable  pytest (>=5.0)
+   :pypi:`pytest-failed-screen-record`              Create a video of the screen when pytest fails                                                                                                                                                                                                                                                                                                                                          Jan 05, 2023    4 - Beta               pytest (>=7.1.2d,<8.0.0)
+   :pypi:`pytest-failed-screenshot`                 Test case fails,take a screenshot,save it,attach it to the allure                                                                                                                                                                                                                                                                                                                       Apr 21, 2021    N/A                    N/A
+   :pypi:`pytest-failed-to-verify`                  A pytest plugin that helps better distinguishing real test failures from setup flakiness.                                                                                                                                                                                                                                                                                               Aug 08, 2019    5 - Production/Stable  pytest (>=4.1.0)
+   :pypi:`pytest-fail-slow`                         Fail tests that take too long to run                                                                                                                                                                                                                                                                                                                                                    Jun 01, 2024    N/A                    pytest>=7.0
+   :pypi:`pytest-failure-tracker`                   A pytest plugin for tracking test failures over multiple runs                                                                                                                                                                                                                                                                                                                           Jul 17, 2024    N/A                    pytest>=6.0.0
+   :pypi:`pytest-faker`                             Faker integration with the pytest framework.                                                                                                                                                                                                                                                                                                                                            Dec 19, 2016    6 - Mature             N/A
+   :pypi:`pytest-falcon`                            Pytest helpers for Falcon.                                                                                                                                                                                                                                                                                                                                                              Sep 07, 2016    4 - Beta               N/A
+   :pypi:`pytest-falcon-client`                     A package to prevent Dependency Confusion attacks against Yandex.                                                                                                                                                                                                                                                                                                                       Feb 21, 2024    N/A                    N/A
+   :pypi:`pytest-fantasy`                           Pytest plugin for Flask Fantasy Framework                                                                                                                                                                                                                                                                                                                                               Mar 14, 2019    N/A                    N/A
+   :pypi:`pytest-fastapi`                                                                                                                                                                                                                                                                                                                                                                                                                   Dec 27, 2020    N/A                    N/A
+   :pypi:`pytest-fastapi-deps`                      A fixture which allows easy replacement of fastapi dependencies for testing                                                                                                                                                                                                                                                                                                             Jul 20, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest-fastest`                           Use SCM and coverage to run only needed tests                                                                                                                                                                                                                                                                                                                                           Oct 04, 2023    4 - Beta               pytest (>=4.4)
+   :pypi:`pytest-fast-first`                        Pytest plugin that runs fast tests first                                                                                                                                                                                                                                                                                                                                                Jan 19, 2023    3 - Alpha              pytest
+   :pypi:`pytest-faulthandler`                      py.test plugin that activates the fault handler module for tests (dummy package)                                                                                                                                                                                                                                                                                                        Jul 04, 2019    6 - Mature             pytest (>=5.0)
+   :pypi:`pytest-fauna`                             A collection of helpful test fixtures for Fauna DB.                                                                                                                                                                                                                                                                                                                                     Jan 03, 2025    N/A                    N/A
+   :pypi:`pytest-fauxfactory`                       Integration of fauxfactory into pytest.                                                                                                                                                                                                                                                                                                                                                 Dec 06, 2017    5 - Production/Stable  pytest (>=3.2)
+   :pypi:`pytest-figleaf`                           py.test figleaf coverage plugin                                                                                                                                                                                                                                                                                                                                                         Jan 18, 2010    5 - Production/Stable  N/A
+   :pypi:`pytest-file`                              Pytest File                                                                                                                                                                                                                                                                                                                                                                             Mar 18, 2024    1 - Planning           N/A
+   :pypi:`pytest-filecov`                           A pytest plugin to detect unused files                                                                                                                                                                                                                                                                                                                                                  Jun 27, 2021    4 - Beta               pytest
+   :pypi:`pytest-filedata`                          easily load test data from files                                                                                                                                                                                                                                                                                                                                                        Apr 29, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-filemarker`                        A pytest plugin that runs marked tests when files change.                                                                                                                                                                                                                                                                                                                               Dec 01, 2020    N/A                    pytest
+   :pypi:`pytest-file-watcher`                      Pytest-File-Watcher is a CLI tool that watches for changes in your code and runs pytest on the changed files.                                                                                                                                                                                                                                                                           Mar 23, 2023    N/A                    pytest
+   :pypi:`pytest-filter-case`                       run test cases filter by mark                                                                                                                                                                                                                                                                                                                                                           Nov 05, 2020    N/A                    N/A
+   :pypi:`pytest-filter-subpackage`                 Pytest plugin for filtering based on sub-packages                                                                                                                                                                                                                                                                                                                                       Mar 04, 2024    5 - Production/Stable  pytest >=4.6
+   :pypi:`pytest-find-dependencies`                 A pytest plugin to find dependencies between tests                                                                                                                                                                                                                                                                                                                                      Mar 16, 2024    4 - Beta               pytest >=4.3.0
+   :pypi:`pytest-finer-verdicts`                    A pytest plugin to treat non-assertion failures as test errors.                                                                                                                                                                                                                                                                                                                         Jun 18, 2020    N/A                    pytest (>=5.4.3)
+   :pypi:`pytest-firefox`                                                                                                                                                                                                                                                                                                                                                                                                                   Feb 28, 2025    N/A                    N/A
+   :pypi:`pytest-fixture-classes`                   Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers                                                                                                                                                                                                                                                                     Sep 02, 2023    5 - Production/Stable  pytest
+   :pypi:`pytest-fixturecollection`                 A pytest plugin to collect tests based on fixtures being used by tests                                                                                                                                                                                                                                                                                                                  Feb 22, 2024    4 - Beta               pytest >=3.5.0
+   :pypi:`pytest-fixture-config`                    Fixture configuration utils for py.test                                                                                                                                                                                                                                                                                                                                                 Oct 17, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-fixture-forms`                     A pytest plugin for creating fixtures that holds different forms between tests.                                                                                                                                                                                                                                                                                                         Dec 06, 2024    N/A                    pytest<9.0.0,>=7.0.0
+   :pypi:`pytest-fixture-maker`                     Pytest plugin to load fixtures from YAML files                                                                                                                                                                                                                                                                                                                                          Sep 21, 2021    N/A                    N/A
+   :pypi:`pytest-fixture-marker`                    A pytest plugin to add markers based on fixtures used.                                                                                                                                                                                                                                                                                                                                  Oct 11, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-fixture-order`                     pytest plugin to control fixture evaluation order                                                                                                                                                                                                                                                                                                                                       May 16, 2022    5 - Production/Stable  pytest (>=3.0)
+   :pypi:`pytest-fixture-ref`                       Lets users reference fixtures without name matching magic.                                                                                                                                                                                                                                                                                                                              Nov 17, 2022    4 - Beta               N/A
+   :pypi:`pytest-fixture-remover`                   A LibCST codemod to remove pytest fixtures applied via the usefixtures decorator, as well as its parametrizations.                                                                                                                                                                                                                                                                      Feb 14, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-fixture-rtttg`                     Warn or fail on fixture name clash                                                                                                                                                                                                                                                                                                                                                      Feb 23, 2022    N/A                    pytest (>=7.0.1,<8.0.0)
+   :pypi:`pytest-fixtures`                          Common fixtures for pytest                                                                                                                                                                                                                                                                                                                                                              May 01, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-fixture-tools`                     Plugin for pytest which provides tools for fixtures                                                                                                                                                                                                                                                                                                                                     Aug 15, 2024    6 - Mature             pytest
+   :pypi:`pytest-fixture-typecheck`                 A pytest plugin to assert type annotations at runtime.                                                                                                                                                                                                                                                                                                                                  Aug 24, 2021    N/A                    pytest
+   :pypi:`pytest-flake8`                            pytest plugin to check FLAKE8 requirements                                                                                                                                                                                                                                                                                                                                              Nov 09, 2024    5 - Production/Stable  pytest>=7.0
+   :pypi:`pytest-flake8-path`                       A pytest fixture for testing flake8 plugins.                                                                                                                                                                                                                                                                                                                                            Oct 25, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-flake8-v2`                         pytest plugin to check FLAKE8 requirements                                                                                                                                                                                                                                                                                                                                              Mar 01, 2022    5 - Production/Stable  pytest (>=7.0)
+   :pypi:`pytest-flake-detection`                   Continuously runs your tests to detect flaky tests                                                                                                                                                                                                                                                                                                                                      Nov 29, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-flakefinder`                       Runs tests multiple times to expose flakiness.                                                                                                                                                                                                                                                                                                                                          Oct 26, 2022    4 - Beta               pytest (>=2.7.1)
+   :pypi:`pytest-flakes`                            pytest plugin to check source code with pyflakes                                                                                                                                                                                                                                                                                                                                        Dec 02, 2021    5 - Production/Stable  pytest (>=5)
+   :pypi:`pytest-flaptastic`                        Flaptastic py.test plugin                                                                                                                                                                                                                                                                                                                                                               Mar 17, 2019    N/A                    N/A
+   :pypi:`pytest-flask`                             A set of py.test fixtures to test Flask applications.                                                                                                                                                                                                                                                                                                                                   Oct 23, 2023    5 - Production/Stable  pytest >=5.2
+   :pypi:`pytest-flask-ligand`                      Pytest fixtures and helper functions to use for testing flask-ligand microservices.                                                                                                                                                                                                                                                                                                     Apr 25, 2023    4 - Beta               pytest (~=7.3)
+   :pypi:`pytest-flask-sqlalchemy`                  A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions.                                                                                                                                                                                                                                                                                          Apr 30, 2022    4 - Beta               pytest (>=3.2.1)
+   :pypi:`pytest-flask-sqlalchemy-transactions`     Run tests in transactions using pytest, Flask, and SQLalchemy.                                                                                                                                                                                                                                                                                                                          Aug 02, 2018    4 - Beta               pytest (>=3.2.1)
+   :pypi:`pytest-flexreport`                                                                                                                                                                                                                                                                                                                                                                                                                Apr 15, 2023    4 - Beta               pytest
+   :pypi:`pytest-fluent`                            A pytest plugin in order to provide logs via fluentd                                                                                                                                                                                                                                                                                                                                    Aug 14, 2024    4 - Beta               pytest>=7.0.0
+   :pypi:`pytest-fluentbit`                         A pytest plugin in order to provide logs via fluentbit                                                                                                                                                                                                                                                                                                                                  Jun 16, 2023    4 - Beta               pytest (>=7.0.0)
+   :pypi:`pytest-fly`                               pytest runner and observer                                                                                                                                                                                                                                                                                                                                                              Mar 20, 2025    3 - Alpha              pytest
+   :pypi:`pytest-flyte`                             Pytest fixtures for simplifying Flyte integration testing                                                                                                                                                                                                                                                                                                                               May 03, 2021    N/A                    pytest
+   :pypi:`pytest-focus`                             A pytest plugin that alerts user of failed test cases with screen notifications                                                                                                                                                                                                                                                                                                         May 04, 2019    4 - Beta               pytest
+   :pypi:`pytest-forbid`                                                                                                                                                                                                                                                                                                                                                                                                                    Mar 07, 2023    N/A                    pytest (>=7.2.2,<8.0.0)
+   :pypi:`pytest-forcefail`                         py.test plugin to make the test failing regardless of pytest.mark.xfail                                                                                                                                                                                                                                                                                                                 May 15, 2018    4 - Beta               N/A
+   :pypi:`pytest-forward-compatability`             A name to avoid typosquating pytest-foward-compatibility                                                                                                                                                                                                                                                                                                                                Sep 06, 2020    N/A                    N/A
+   :pypi:`pytest-forward-compatibility`             A pytest plugin to shim pytest commandline options for fowards compatibility                                                                                                                                                                                                                                                                                                            Sep 29, 2020    N/A                    N/A
+   :pypi:`pytest-frappe`                            Pytest Frappe Plugin - A set of pytest fixtures to test Frappe applications                                                                                                                                                                                                                                                                                                             Jul 30, 2024    4 - Beta               pytest>=7.0.0
+   :pypi:`pytest-freethreaded`                      pytest plugin for running parallel tests                                                                                                                                                                                                                                                                                                                                                Oct 03, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-freezeblaster`                     Wrap tests with fixtures in freeze_time                                                                                                                                                                                                                                                                                                                                                 Feb 11, 2025    N/A                    pytest>=6.2.5
+   :pypi:`pytest-freezegun`                         Wrap tests with fixtures in freeze_time                                                                                                                                                                                                                                                                                                                                                 Jul 19, 2020    4 - Beta               pytest (>=3.0.0)
+   :pypi:`pytest-freezer`                           Pytest plugin providing a fixture interface for spulec/freezegun                                                                                                                                                                                                                                                                                                                        Dec 12, 2024    N/A                    pytest>=3.6
+   :pypi:`pytest-freeze-reqs`                       Check if requirement files are frozen                                                                                                                                                                                                                                                                                                                                                   Apr 29, 2021    N/A                    N/A
+   :pypi:`pytest-frozen-uuids`                      Deterministically frozen UUID's for your tests                                                                                                                                                                                                                                                                                                                                          Apr 17, 2022    N/A                    pytest (>=3.0)
+   :pypi:`pytest-func-cov`                          Pytest plugin for measuring function coverage                                                                                                                                                                                                                                                                                                                                           Apr 15, 2021    3 - Alpha              pytest (>=5)
+   :pypi:`pytest-funcnodes`                         Testing plugin for funcnodes                                                                                                                                                                                                                                                                                                                                                            Mar 19, 2025    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-funparam`                          An alternative way to parametrize test cases.                                                                                                                                                                                                                                                                                                                                           Dec 02, 2021    4 - Beta               pytest >=4.6.0
+   :pypi:`pytest-fv`                                pytest extensions to support running functional-verification jobs                                                                                                                                                                                                                                                                                                                       Feb 27, 2025    N/A                    pytest
+   :pypi:`pytest-fxa`                               pytest plugin for Firefox Accounts                                                                                                                                                                                                                                                                                                                                                      Aug 28, 2018    5 - Production/Stable  N/A
+   :pypi:`pytest-fxa-mte`                           pytest plugin for Firefox Accounts                                                                                                                                                                                                                                                                                                                                                      Oct 02, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-fxtest`                                                                                                                                                                                                                                                                                                                                                                                                                    Oct 27, 2020    N/A                    N/A
+   :pypi:`pytest-fzf`                               fzf-based test selector for pytest                                                                                                                                                                                                                                                                                                                                                      Jan 06, 2025    4 - Beta               pytest>=6.0.0
+   :pypi:`pytest_gae`                               pytest plugin for apps written with Google's AppEngine                                                                                                                                                                                                                                                                                                                                  Aug 03, 2016    3 - Alpha              N/A
+   :pypi:`pytest-gak`                               A Pytest plugin and command line tool for interactive testing with Pytest                                                                                                                                                                                                                                                                                                               Mar 29, 2025    N/A                    N/A
+   :pypi:`pytest-gather-fixtures`                   set up asynchronous pytest fixtures concurrently                                                                                                                                                                                                                                                                                                                                        Aug 18, 2024    N/A                    pytest>=7.0.0
+   :pypi:`pytest-gc`                                The garbage collector plugin for py.test                                                                                                                                                                                                                                                                                                                                                Feb 01, 2018    N/A                    N/A
+   :pypi:`pytest-gcov`                              Uses gcov to measure test coverage of a C library                                                                                                                                                                                                                                                                                                                                       Feb 01, 2018    3 - Alpha              N/A
+   :pypi:`pytest-gcs`                               GCS fixtures and fixture factories for Pytest.                                                                                                                                                                                                                                                                                                                                          Jan 24, 2025    5 - Production/Stable  pytest>=6.2
+   :pypi:`pytest-gee`                               The Python plugin for your GEE based packages.                                                                                                                                                                                                                                                                                                                                          Feb 20, 2025    3 - Alpha              pytest
+   :pypi:`pytest-gevent`                            Ensure that gevent is properly patched when invoking pytest                                                                                                                                                                                                                                                                                                                             Feb 25, 2020    N/A                    pytest
+   :pypi:`pytest-gherkin`                           A flexible framework for executing BDD gherkin tests                                                                                                                                                                                                                                                                                                                                    Jul 27, 2019    3 - Alpha              pytest (>=5.0.0)
+   :pypi:`pytest-gh-log-group`                      pytest plugin for gh actions                                                                                                                                                                                                                                                                                                                                                            Jan 11, 2022    3 - Alpha              pytest
+   :pypi:`pytest-ghostinspector`                    For finding/executing Ghost Inspector tests                                                                                                                                                                                                                                                                                                                                             May 17, 2016    3 - Alpha              N/A
+   :pypi:`pytest-girder`                            A set of pytest fixtures for testing Girder applications.                                                                                                                                                                                                                                                                                                                               Apr 04, 2025    N/A                    pytest>=3.6
+   :pypi:`pytest-git`                               Git repository fixture for py.test                                                                                                                                                                                                                                                                                                                                                      Oct 17, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-gitconfig`                         Provide a Git config sandbox for testing                                                                                                                                                                                                                                                                                                                                                Aug 11, 2024    4 - Beta               pytest>=7.1.2
+   :pypi:`pytest-gitcov`                            Pytest plugin for reporting on coverage of the last git commit.                                                                                                                                                                                                                                                                                                                         Jan 11, 2020    2 - Pre-Alpha          N/A
+   :pypi:`pytest-git-diff`                          Pytest plugin that allows the user to select the tests affected by a range of git commits                                                                                                                                                                                                                                                                                               Apr 02, 2024    N/A                    N/A
+   :pypi:`pytest-git-fixtures`                      Pytest fixtures for testing with git.                                                                                                                                                                                                                                                                                                                                                   Mar 11, 2021    4 - Beta               pytest
+   :pypi:`pytest-github`                            Plugin for py.test that associates tests with github issues using a marker.                                                                                                                                                                                                                                                                                                             Mar 07, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-github-actions-annotate-failures`  pytest plugin to annotate failed tests with a workflow command for GitHub Actions                                                                                                                                                                                                                                                                                                       Jan 17, 2025    5 - Production/Stable  pytest>=6.0.0
+   :pypi:`pytest-github-report`                     Generate a GitHub report using pytest in GitHub Workflows                                                                                                                                                                                                                                                                                                                               Jun 03, 2022    4 - Beta               N/A
+   :pypi:`pytest-gitignore`                         py.test plugin to ignore the same files as git                                                                                                                                                                                                                                                                                                                                          Jul 17, 2015    4 - Beta               N/A
+   :pypi:`pytest-gitlab`                            Pytest Plugin for Gitlab                                                                                                                                                                                                                                                                                                                                                                Oct 16, 2024    N/A                    N/A
+   :pypi:`pytest-gitlabci-parallelized`             Parallelize pytest across GitLab CI workers.                                                                                                                                                                                                                                                                                                                                            Mar 08, 2023    N/A                    N/A
+   :pypi:`pytest-gitlab-code-quality`               Collects warnings while testing and generates a GitLab Code Quality Report.                                                                                                                                                                                                                                                                                                             Sep 09, 2024    N/A                    pytest>=8.1.1
+   :pypi:`pytest-gitlab-fold`                       Folds output sections in GitLab CI build log                                                                                                                                                                                                                                                                                                                                            Dec 31, 2023    4 - Beta               pytest >=2.6.0
+   :pypi:`pytest-git-selector`                      Utility to select tests that have had its dependencies modified (as identified by git diff)                                                                                                                                                                                                                                                                                             Nov 17, 2022    N/A                    N/A
+   :pypi:`pytest-glamor-allure`                     Extends allure-pytest functionality                                                                                                                                                                                                                                                                                                                                                     Apr 30, 2024    4 - Beta               pytest<=8.2.0
+   :pypi:`pytest-gnupg-fixtures`                    Pytest fixtures for testing with gnupg.                                                                                                                                                                                                                                                                                                                                                 Mar 04, 2021    4 - Beta               pytest
+   :pypi:`pytest-golden`                            Plugin for pytest that offloads expected outputs to data files                                                                                                                                                                                                                                                                                                                          Jul 18, 2022    N/A                    pytest (>=6.1.2)
+   :pypi:`pytest-goldie`                            A plugin to support golden tests with pytest.                                                                                                                                                                                                                                                                                                                                           May 23, 2023    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-google-chat`                       Notify google chat channel for test results                                                                                                                                                                                                                                                                                                                                             Mar 27, 2022    4 - Beta               pytest
+   :pypi:`pytest-graphql-schema`                    Get graphql schema as fixture for pytest                                                                                                                                                                                                                                                                                                                                                Oct 18, 2019    N/A                    N/A
+   :pypi:`pytest-greendots`                         Green progress dots                                                                                                                                                                                                                                                                                                                                                                     Feb 08, 2014    3 - Alpha              N/A
+   :pypi:`pytest-group-by-class`                    A Pytest plugin for running a subset of your tests by splitting them in to groups of classes.                                                                                                                                                                                                                                                                                           Jun 27, 2023    5 - Production/Stable  pytest (>=2.5)
+   :pypi:`pytest-growl`                             Growl notifications for pytest results.                                                                                                                                                                                                                                                                                                                                                 Jan 13, 2014    5 - Production/Stable  N/A
+   :pypi:`pytest-grpc`                              pytest plugin for grpc                                                                                                                                                                                                                                                                                                                                                                  May 01, 2020    N/A                    pytest (>=3.6.0)
+   :pypi:`pytest-grunnur`                           Py.Test plugin for Grunnur-based packages.                                                                                                                                                                                                                                                                                                                                              Jul 26, 2024    N/A                    pytest>=6
+   :pypi:`pytest_gui_status`                        Show pytest status in gui                                                                                                                                                                                                                                                                                                                                                               Jan 23, 2016    N/A                    pytest
+   :pypi:`pytest-hammertime`                        Display "🔨 " instead of "." for passed pytest tests.                                                                                                                                                                                                                                                                                                                                   Jul 28, 2018    N/A                    pytest
+   :pypi:`pytest-hardware-test-report`              A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Apr 01, 2024    4 - Beta               pytest<9.0.0,>=8.0.0
+   :pypi:`pytest-harmony`                           Chain tests and data with pytest                                                                                                                                                                                                                                                                                                                                                        Jan 17, 2023    N/A                    pytest (>=7.2.1,<8.0.0)
+   :pypi:`pytest-harvest`                           Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes.                                                                                                                                                                                                                                           Mar 16, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-helm-charts`                       A plugin to provide different types and configs of Kubernetes clusters that can be used for testing.                                                                                                                                                                                                                                                                                    Oct 31, 2024    4 - Beta               pytest<9.0.0,>=8.0.0
+   :pypi:`pytest-helm-templates`                    Pytest fixtures for unit testing the output of helm templates                                                                                                                                                                                                                                                                                                                           Aug 07, 2024    N/A                    pytest~=7.4.0; extra == "dev"
+   :pypi:`pytest-helper`                            Functions to help in using the pytest testing framework                                                                                                                                                                                                                                                                                                                                 May 31, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-helpers`                           pytest helpers                                                                                                                                                                                                                                                                                                                                                                          May 17, 2020    N/A                    pytest
+   :pypi:`pytest-helpers-namespace`                 Pytest Helpers Namespace Plugin                                                                                                                                                                                                                                                                                                                                                         Dec 29, 2021    5 - Production/Stable  pytest (>=6.0.0)
+   :pypi:`pytest-henry`                                                                                                                                                                                                                                                                                                                                                                                                                     Aug 29, 2023    N/A                    N/A
+   :pypi:`pytest-hidecaptured`                      Hide captured output                                                                                                                                                                                                                                                                                                                                                                    May 04, 2018    4 - Beta               pytest (>=2.8.5)
+   :pypi:`pytest-himark`                            This plugin aims to create markers automatically based on a json configuration.                                                                                                                                                                                                                                                                                                         Jun 05, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-historic`                          Custom report to display pytest historical execution records                                                                                                                                                                                                                                                                                                                            Apr 08, 2020    N/A                    pytest
+   :pypi:`pytest-historic-hook`                     Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report                                                                                                                                                                                                                                                                                      Apr 08, 2020    N/A                    pytest
+   :pypi:`pytest-history`                           Pytest plugin to keep a history of your pytest runs                                                                                                                                                                                                                                                                                                                                     Jan 14, 2024    N/A                    pytest (>=7.4.3,<8.0.0)
+   :pypi:`pytest-home`                              Home directory fixtures                                                                                                                                                                                                                                                                                                                                                                 Jul 28, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-homeassistant`                     A pytest plugin for use with homeassistant custom components.                                                                                                                                                                                                                                                                                                                           Aug 12, 2020    4 - Beta               N/A
+   :pypi:`pytest-homeassistant-custom-component`    Experimental package to automatically extract test plugins for Home Assistant custom components                                                                                                                                                                                                                                                                                         Apr 05, 2025    3 - Alpha              pytest==8.3.5
+   :pypi:`pytest-honey`                             A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Jan 07, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-honors`                            Report on tests that honor constraints, and guard against regressions                                                                                                                                                                                                                                                                                                                   Mar 06, 2020    4 - Beta               N/A
+   :pypi:`pytest-hot-reloading`                                                                                                                                                                                                                                                                                                                                                                                                             Sep 23, 2024    N/A                    N/A
+   :pypi:`pytest-hot-test`                          A plugin that tracks test changes                                                                                                                                                                                                                                                                                                                                                       Dec 10, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-houdini`                           pytest plugin for testing code in Houdini.                                                                                                                                                                                                                                                                                                                                              Jul 15, 2024    N/A                    pytest
+   :pypi:`pytest-hoverfly`                          Simplify working with Hoverfly from pytest                                                                                                                                                                                                                                                                                                                                              Jan 30, 2023    N/A                    pytest (>=5.0)
+   :pypi:`pytest-hoverfly-wrapper`                  Integrates the Hoverfly HTTP proxy into Pytest                                                                                                                                                                                                                                                                                                                                          Feb 27, 2023    5 - Production/Stable  pytest (>=3.7.0)
+   :pypi:`pytest-hpfeeds`                           Helpers for testing hpfeeds in your python project                                                                                                                                                                                                                                                                                                                                      Feb 28, 2023    4 - Beta               pytest (>=6.2.4,<7.0.0)
+   :pypi:`pytest-html`                              pytest plugin for generating HTML reports                                                                                                                                                                                                                                                                                                                                               Nov 07, 2023    5 - Production/Stable  pytest>=7.0.0
+   :pypi:`pytest-html-cn`                           pytest plugin for generating HTML reports                                                                                                                                                                                                                                                                                                                                               Aug 19, 2024    5 - Production/Stable  pytest!=6.0.0,>=5.0
+   :pypi:`pytest-html-lee`                          optimized pytest plugin for generating HTML reports                                                                                                                                                                                                                                                                                                                                     Jun 30, 2020    5 - Production/Stable  pytest (>=5.0)
+   :pypi:`pytest-html-merger`                       Pytest HTML reports merging utility                                                                                                                                                                                                                                                                                                                                                     Jul 12, 2024    N/A                    N/A
+   :pypi:`pytest-html-object-storage`               Pytest report plugin for send HTML report on object-storage                                                                                                                                                                                                                                                                                                                             Jan 17, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-html-profiling`                    Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt.                                                                                                                                                                                                                                          Feb 11, 2020    5 - Production/Stable  pytest (>=3.0)
+   :pypi:`pytest-html-reporter`                     Generates a static html report based on pytest framework                                                                                                                                                                                                                                                                                                                                Feb 13, 2022    N/A                    N/A
+   :pypi:`pytest-html-report-merger`                                                                                                                                                                                                                                                                                                                                                                                                        May 22, 2024    N/A                    N/A
+   :pypi:`pytest-html-thread`                       pytest plugin for generating HTML reports                                                                                                                                                                                                                                                                                                                                               Dec 29, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-http`                              Fixture "http" for http requests                                                                                                                                                                                                                                                                                                                                                        Aug 22, 2024    N/A                    pytest
+   :pypi:`pytest-httpbin`                           Easily test your HTTP library against a local copy of httpbin                                                                                                                                                                                                                                                                                                                           Sep 18, 2024    5 - Production/Stable  pytest; extra == "test"
+   :pypi:`pytest-httpdbg`                           A pytest plugin to record HTTP(S) requests with stack trace.                                                                                                                                                                                                                                                                                                                            Feb 11, 2025    4 - Beta               pytest>=7.0.0
+   :pypi:`pytest-http-mocker`                       Pytest plugin for http mocking (via https://github.com/vilus/mocker)                                                                                                                                                                                                                                                                                                                    Oct 20, 2019    N/A                    N/A
+   :pypi:`pytest-httpretty`                         A thin wrapper of HTTPretty for pytest                                                                                                                                                                                                                                                                                                                                                  Feb 16, 2014    3 - Alpha              N/A
+   :pypi:`pytest_httpserver`                        pytest-httpserver is a httpserver for pytest                                                                                                                                                                                                                                                                                                                                            Feb 24, 2025    3 - Alpha              N/A
+   :pypi:`pytest-httptesting`                       http_testing framework on top of pytest                                                                                                                                                                                                                                                                                                                                                 Dec 19, 2024    N/A                    pytest>=8.2.0
+   :pypi:`pytest-httpx`                             Send responses to httpx.                                                                                                                                                                                                                                                                                                                                                                Nov 28, 2024    5 - Production/Stable  pytest==8.*
+   :pypi:`pytest-httpx-blockage`                    Disable httpx requests during a test run                                                                                                                                                                                                                                                                                                                                                Feb 16, 2023    N/A                    pytest (>=7.2.1)
+   :pypi:`pytest-httpx-recorder`                    Recorder feature based on pytest_httpx, like recorder feature in responses.                                                                                                                                                                                                                                                                                                             Jan 04, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-hue`                               Visualise PyTest status via your Phillips Hue lights                                                                                                                                                                                                                                                                                                                                    May 09, 2019    N/A                    N/A
+   :pypi:`pytest-hylang`                            Pytest plugin to allow running tests written in hylang                                                                                                                                                                                                                                                                                                                                  Mar 28, 2021    N/A                    pytest
+   :pypi:`pytest-hypo-25`                           help hypo module for pytest                                                                                                                                                                                                                                                                                                                                                             Jan 12, 2020    3 - Alpha              N/A
+   :pypi:`pytest-iam`                               A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite                                                                                                                                                                                                                                                                                                   Apr 04, 2025    4 - Beta               pytest>=7.0.0
+   :pypi:`pytest-ibutsu`                            A plugin to sent pytest results to an Ibutsu server                                                                                                                                                                                                                                                                                                                                     Feb 06, 2025    4 - Beta               pytest>=7.1
+   :pypi:`pytest-icdiff`                            use icdiff for better error messages in pytest assertions                                                                                                                                                                                                                                                                                                                               Dec 05, 2023    4 - Beta               pytest
+   :pypi:`pytest-idapro`                            A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api                                                                                                                                                                                                                Nov 03, 2018    N/A                    N/A
+   :pypi:`pytest-idem`                              A pytest plugin to help with testing idem projects                                                                                                                                                                                                                                                                                                                                      Dec 13, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-idempotent`                        Pytest plugin for testing function idempotence.                                                                                                                                                                                                                                                                                                                                         Jul 25, 2022    N/A                    N/A
+   :pypi:`pytest-ignore-flaky`                      ignore failures from flaky tests (pytest plugin)                                                                                                                                                                                                                                                                                                                                        Apr 20, 2024    5 - Production/Stable  pytest>=6.0
+   :pypi:`pytest-ignore-test-results`               A pytest plugin to ignore test results.                                                                                                                                                                                                                                                                                                                                                 Feb 03, 2025    5 - Production/Stable  pytest>=7.0
+   :pypi:`pytest-image-diff`                                                                                                                                                                                                                                                                                                                                                                                                                Dec 31, 2024    3 - Alpha              pytest
+   :pypi:`pytest-image-snapshot`                    A pytest plugin for image snapshot management and comparison.                                                                                                                                                                                                                                                                                                                           Jul 01, 2024    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-import-check`                      pytest plugin to check whether Python modules can be imported                                                                                                                                                                                                                                                                                                                           Jul 19, 2024    3 - Alpha              pytest>=8.1
+   :pypi:`pytest-incremental`                       an incremental test runner (pytest plugin)                                                                                                                                                                                                                                                                                                                                              Apr 24, 2021    5 - Production/Stable  N/A
+   :pypi:`pytest-infinity`                                                                                                                                                                                                                                                                                                                                                                                                                  Jun 09, 2024    N/A                    pytest<9.0.0,>=8.0.0
+   :pypi:`pytest-influx`                            Pytest plugin for managing your influx instance between test runs                                                                                                                                                                                                                                                                                                                       Oct 16, 2024    N/A                    pytest<9.0.0,>=8.3.3
+   :pypi:`pytest-influxdb`                          Plugin for influxdb and pytest integration.                                                                                                                                                                                                                                                                                                                                             Apr 20, 2021    N/A                    N/A
+   :pypi:`pytest-info-collector`                    pytest plugin to collect information from tests                                                                                                                                                                                                                                                                                                                                         May 26, 2019    3 - Alpha              N/A
+   :pypi:`pytest-info-plugin`                       Get executed interface information in pytest interface automation framework                                                                                                                                                                                                                                                                                                             Sep 14, 2023    N/A                    N/A
+   :pypi:`pytest-informative-node`                  display more node ininformation.                                                                                                                                                                                                                                                                                                                                                        Apr 25, 2019    4 - Beta               N/A
+   :pypi:`pytest-infrahouse`                        A set of fixtures to use with pytest                                                                                                                                                                                                                                                                                                                                                    Mar 18, 2025    4 - Beta               pytest~=8.3
+   :pypi:`pytest-infrastructure`                    pytest stack validation prior to testing executing                                                                                                                                                                                                                                                                                                                                      Apr 12, 2020    4 - Beta               N/A
+   :pypi:`pytest-ini`                               Reuse pytest.ini to store env variables                                                                                                                                                                                                                                                                                                                                                 Apr 26, 2022    N/A                    N/A
+   :pypi:`pytest-initry`                            Plugin for sending automation test data from Pytest to the initry                                                                                                                                                                                                                                                                                                                       Apr 30, 2024    N/A                    pytest<9.0.0,>=8.1.1
+   :pypi:`pytest-inline`                            A pytest plugin for writing inline tests                                                                                                                                                                                                                                                                                                                                                Oct 24, 2024    4 - Beta               pytest<9.0,>=7.0
+   :pypi:`pytest-inmanta`                           A py.test plugin providing fixtures to simplify inmanta modules testing.                                                                                                                                                                                                                                                                                                                Oct 10, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-inmanta-extensions`                Inmanta tests package                                                                                                                                                                                                                                                                                                                                                                   Jan 17, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-inmanta-lsm`                       Common fixtures for inmanta LSM related modules                                                                                                                                                                                                                                                                                                                                         Dec 13, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-inmanta-yang`                      Common fixtures used in inmanta yang related modules                                                                                                                                                                                                                                                                                                                                    Feb 22, 2024    4 - Beta               pytest
+   :pypi:`pytest-Inomaly`                           A simple image diff plugin for pytest                                                                                                                                                                                                                                                                                                                                                   Feb 13, 2018    4 - Beta               N/A
+   :pypi:`pytest-in-robotframework`                 The extension enables easy execution of pytest tests within the Robot Framework environment.                                                                                                                                                                                                                                                                                            Nov 23, 2024    N/A                    pytest
+   :pypi:`pytest-insper`                            Pytest plugin for courses at Insper                                                                                                                                                                                                                                                                                                                                                     Mar 21, 2024    N/A                    pytest
+   :pypi:`pytest-insta`                             A practical snapshot testing plugin for pytest                                                                                                                                                                                                                                                                                                                                          Feb 19, 2024    N/A                    pytest (>=7.2.0,<9.0.0)
+   :pypi:`pytest-instafail`                         pytest plugin to show failures instantly                                                                                                                                                                                                                                                                                                                                                Mar 31, 2023    4 - Beta               pytest (>=5)
+   :pypi:`pytest-instrument`                        pytest plugin to instrument tests                                                                                                                                                                                                                                                                                                                                                       Apr 05, 2020    5 - Production/Stable  pytest (>=5.1.0)
+   :pypi:`pytest-integration`                       Organizing pytests by integration or not                                                                                                                                                                                                                                                                                                                                                Nov 17, 2022    N/A                    N/A
+   :pypi:`pytest-integration-mark`                  Automatic integration test marking and excluding plugin for pytest                                                                                                                                                                                                                                                                                                                      May 22, 2023    N/A                    pytest (>=5.2)
+   :pypi:`pytest-interactive`                       A pytest plugin for console based interactive test selection just after the collection phase                                                                                                                                                                                                                                                                                            Nov 30, 2017    3 - Alpha              N/A
+   :pypi:`pytest-intercept-remote`                  Pytest plugin for intercepting outgoing connection requests during pytest run.                                                                                                                                                                                                                                                                                                          May 24, 2021    4 - Beta               pytest (>=4.6)
+   :pypi:`pytest-interface-tester`                  Pytest plugin for checking charm relation interface protocol compliance.                                                                                                                                                                                                                                                                                                                Feb 13, 2025    4 - Beta               pytest
+   :pypi:`pytest-invenio`                           Pytest fixtures for Invenio.                                                                                                                                                                                                                                                                                                                                                            Apr 02, 2025    5 - Production/Stable  pytest<9.0.0,>=6
+   :pypi:`pytest-involve`                           Run tests covering a specific file or changeset                                                                                                                                                                                                                                                                                                                                         Feb 02, 2020    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-iovis`                             A Pytest plugin to enable Jupyter Notebook testing with Papermill                                                                                                                                                                                                                                                                                                                       Nov 06, 2024    4 - Beta               pytest>=7.1.0
+   :pypi:`pytest-ipdb`                              A py.test plug-in to enable drop to ipdb debugger on test failure.                                                                                                                                                                                                                                                                                                                      Mar 20, 2013    2 - Pre-Alpha          N/A
+   :pypi:`pytest-ipynb`                             THIS PROJECT IS ABANDONED                                                                                                                                                                                                                                                                                                                                                               Jan 29, 2019    3 - Alpha              N/A
+   :pypi:`pytest-ipynb2`                            Pytest plugin to run tests in Jupyter Notebooks                                                                                                                                                                                                                                                                                                                                         Mar 09, 2025    N/A                    pytest
+   :pypi:`pytest-ipywidgets`                                                                                                                                                                                                                                                                                                                                                                                                                Feb 18, 2025    N/A                    pytest
+   :pypi:`pytest-isolate`                           Run pytest tests in isolated subprocesses                                                                                                                                                                                                                                                                                                                                               Jan 16, 2025    4 - Beta               pytest
+   :pypi:`pytest-isolate-mpi`                       pytest-isolate-mpi allows for MPI-parallel tests being executed in a segfault and MPI_Abort safe manner                                                                                                                                                                                                                                                                                 Feb 24, 2025    4 - Beta               pytest>=5
+   :pypi:`pytest-isort`                             py.test plugin to check import ordering using isort                                                                                                                                                                                                                                                                                                                                     Mar 05, 2024    5 - Production/Stable  pytest (>=5.0)
+   :pypi:`pytest-it`                                Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it.                                                                                                                                                                                                                                                                    Jan 29, 2024    4 - Beta               N/A
+   :pypi:`pytest-item-dict`                         Get a hierarchical dict of session.items                                                                                                                                                                                                                                                                                                                                                Nov 14, 2024    4 - Beta               pytest>=8.3.0
+   :pypi:`pytest-iterassert`                        Nicer list and iterable assertion messages for pytest                                                                                                                                                                                                                                                                                                                                   May 11, 2020    3 - Alpha              N/A
+   :pypi:`pytest-iteration`                         Add iteration mark for tests                                                                                                                                                                                                                                                                                                                                                            Aug 22, 2024    N/A                    pytest
+   :pypi:`pytest-iters`                             A contextmanager pytest fixture for handling multiple mock iters                                                                                                                                                                                                                                                                                                                        May 24, 2022    N/A                    N/A
+   :pypi:`pytest_jar_yuan`                          A allure and pytest used package                                                                                                                                                                                                                                                                                                                                                        Dec 12, 2022    N/A                    N/A
+   :pypi:`pytest-jasmine`                           Run jasmine tests from your pytest test suite                                                                                                                                                                                                                                                                                                                                           Nov 04, 2017    1 - Planning           N/A
+   :pypi:`pytest-jelastic`                          Pytest plugin defining the necessary command-line options to pass to pytests testing a Jelastic environment.                                                                                                                                                                                                                                                                            Nov 16, 2022    N/A                    pytest (>=7.2.0,<8.0.0)
+   :pypi:`pytest-jest`                              A custom jest-pytest oriented Pytest reporter                                                                                                                                                                                                                                                                                                                                           May 22, 2018    4 - Beta               pytest (>=3.3.2)
+   :pypi:`pytest-jinja`                             A plugin to generate customizable jinja-based HTML reports in pytest                                                                                                                                                                                                                                                                                                                    Oct 04, 2022    3 - Alpha              pytest (>=6.2.5,<7.0.0)
+   :pypi:`pytest-jira`                              py.test JIRA integration plugin, using markers                                                                                                                                                                                                                                                                                                                                          Apr 30, 2024    3 - Alpha              N/A
+   :pypi:`pytest-jira-xfail`                        Plugin skips (xfail) tests if unresolved Jira issue(s) linked                                                                                                                                                                                                                                                                                                                           Jul 09, 2024    N/A                    pytest>=7.2.0
+   :pypi:`pytest-jira-xray`                         pytest plugin to integrate tests with JIRA XRAY                                                                                                                                                                                                                                                                                                                                         Oct 27, 2024    4 - Beta               pytest>=6.2.4
+   :pypi:`pytest-job-selection`                     A pytest plugin for load balancing test suites                                                                                                                                                                                                                                                                                                                                          Jan 30, 2023    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-jobserver`                         Limit parallel tests with posix jobserver.                                                                                                                                                                                                                                                                                                                                              May 15, 2019    5 - Production/Stable  pytest
+   :pypi:`pytest-joke`                              Test failures are better served with humor.                                                                                                                                                                                                                                                                                                                                             Oct 08, 2019    4 - Beta               pytest (>=4.2.1)
+   :pypi:`pytest-json`                              Generate JSON test reports                                                                                                                                                                                                                                                                                                                                                              Jan 18, 2016    4 - Beta               N/A
+   :pypi:`pytest-json-ctrf`                         Pytest plugin to generate json report in CTRF (Common Test Report Format)                                                                                                                                                                                                                                                                                                               Oct 10, 2024    N/A                    pytest>6.0.0
+   :pypi:`pytest-json-fixtures`                     JSON output for the --fixtures flag                                                                                                                                                                                                                                                                                                                                                     Mar 14, 2023    4 - Beta               N/A
+   :pypi:`pytest-jsonlint`                          UNKNOWN                                                                                                                                                                                                                                                                                                                                                                                 Aug 04, 2016    N/A                    N/A
+   :pypi:`pytest-json-report`                       A pytest plugin to report test results as JSON files                                                                                                                                                                                                                                                                                                                                    Mar 15, 2022    4 - Beta               pytest (>=3.8.0)
+   :pypi:`pytest-json-report-wip`                   A pytest plugin to report test results as JSON files                                                                                                                                                                                                                                                                                                                                    Oct 28, 2023    4 - Beta               pytest >=3.8.0
+   :pypi:`pytest-jsonschema`                        A pytest plugin to perform JSONSchema validations                                                                                                                                                                                                                                                                                                                                       Mar 27, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-jtr`                               pytest plugin supporting json test report output                                                                                                                                                                                                                                                                                                                                        Jul 21, 2024    N/A                    pytest<8.0.0,>=7.1.2
+   :pypi:`pytest-junit-xray-xml`                    Export test results in an augmented JUnit format for usage with Xray ()                                                                                                                                                                                                                                                                                                                 Jan 01, 2025    4 - Beta               pytest
+   :pypi:`pytest-jupyter`                           A pytest plugin for testing Jupyter libraries and extensions.                                                                                                                                                                                                                                                                                                                           Apr 04, 2024    4 - Beta               pytest>=7.0
+   :pypi:`pytest-jupyterhub`                        A reusable JupyterHub pytest plugin                                                                                                                                                                                                                                                                                                                                                     Apr 25, 2023    5 - Production/Stable  pytest
+   :pypi:`pytest-kafka`                             Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest                                                                                                                                                                                                                                                                                                                         Aug 14, 2024    N/A                    pytest
+   :pypi:`pytest-kafkavents`                        A plugin to send pytest events to Kafka                                                                                                                                                                                                                                                                                                                                                 Sep 08, 2021    4 - Beta               pytest
+   :pypi:`pytest-kairos`                            Pytest plugin with random number generation, reproducibility, and test repetition                                                                                                                                                                                                                                                                                                       Aug 08, 2024    5 - Production/Stable  pytest>=5.0.0
+   :pypi:`pytest-kasima`                            Display horizontal lines above and below the captured standard output for easy viewing.                                                                                                                                                                                                                                                                                                 Jan 26, 2023    5 - Production/Stable  pytest (>=7.2.1,<8.0.0)
+   :pypi:`pytest-keep-together`                     Pytest plugin to customize test ordering by running all 'related' tests together                                                                                                                                                                                                                                                                                                        Dec 07, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest-kexi`                                                                                                                                                                                                                                                                                                                                                                                                                      Apr 29, 2022    N/A                    pytest (>=7.1.2,<8.0.0)
+   :pypi:`pytest-keyring`                           A Pytest plugin to access the system's keyring to provide credentials for tests                                                                                                                                                                                                                                                                                                         Dec 08, 2024    N/A                    pytest>=8.0.2
+   :pypi:`pytest-kind`                              Kubernetes test support with KIND for pytest                                                                                                                                                                                                                                                                                                                                            Nov 30, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-kivy`                              Kivy GUI tests fixtures using pytest                                                                                                                                                                                                                                                                                                                                                    Jul 06, 2021    4 - Beta               pytest (>=3.6)
+   :pypi:`pytest-knows`                             A pytest plugin that can automaticly skip test case based on dependence info calculated by trace                                                                                                                                                                                                                                                                                        Aug 22, 2014    N/A                    N/A
+   :pypi:`pytest-konira`                            Run Konira DSL tests with py.test                                                                                                                                                                                                                                                                                                                                                       Oct 09, 2011    N/A                    N/A
+   :pypi:`pytest-kookit`                            Your simple but kooky integration testing with pytest                                                                                                                                                                                                                                                                                                                                   Sep 10, 2024    N/A                    N/A
+   :pypi:`pytest-koopmans`                          A plugin for testing the koopmans package                                                                                                                                                                                                                                                                                                                                               Nov 21, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-krtech-common`                     pytest krtech common library                                                                                                                                                                                                                                                                                                                                                            Nov 28, 2016    4 - Beta               N/A
+   :pypi:`pytest-kubernetes`                                                                                                                                                                                                                                                                                                                                                                                                                Feb 04, 2025    N/A                    pytest<9.0.0,>=8.3.0
+   :pypi:`pytest-kuunda`                            pytest plugin to help with test data setup for PySpark tests                                                                                                                                                                                                                                                                                                                            Feb 25, 2024    4 - Beta               pytest >=6.2.0
+   :pypi:`pytest-kwparametrize`                     Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks                                                                                                                                                                                                                                                                               Jan 22, 2021    N/A                    pytest (>=6)
+   :pypi:`pytest-lambda`                            Define pytest fixtures with lambda functions.                                                                                                                                                                                                                                                                                                                                           May 27, 2024    5 - Production/Stable  pytest<9,>=3.6
+   :pypi:`pytest-lamp`                                                                                                                                                                                                                                                                                                                                                                                                                      Jan 06, 2017    3 - Alpha              N/A
+   :pypi:`pytest-langchain`                         Pytest-style test runner for langchain agents                                                                                                                                                                                                                                                                                                                                           Feb 26, 2023    N/A                    pytest
+   :pypi:`pytest-lark`                              Create fancy and clear HTML test reports.                                                                                                                                                                                                                                                                                                                                               Nov 05, 2023    N/A                    N/A
+   :pypi:`pytest-latin-hypercube`                   Implementation of Latin Hypercube Sampling for pytest.                                                                                                                                                                                                                                                                                                                                  Feb 27, 2025    N/A                    pytest
+   :pypi:`pytest-launchable`                        Launchable Pytest Plugin                                                                                                                                                                                                                                                                                                                                                                Apr 05, 2023    N/A                    pytest (>=4.2.0)
+   :pypi:`pytest-layab`                             Pytest fixtures for layab.                                                                                                                                                                                                                                                                                                                                                              Oct 05, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-lazy-fixture`                      It helps to use fixtures in pytest.mark.parametrize                                                                                                                                                                                                                                                                                                                                     Feb 01, 2020    4 - Beta               pytest (>=3.2.5)
+   :pypi:`pytest-lazy-fixtures`                     Allows you to use fixtures in @pytest.mark.parametrize.                                                                                                                                                                                                                                                                                                                                 Jan 25, 2025    N/A                    pytest>=7
+   :pypi:`pytest-ldap`                              python-ldap fixtures for pytest                                                                                                                                                                                                                                                                                                                                                         Aug 18, 2020    N/A                    pytest
+   :pypi:`pytest-leak-finder`                       Find the test that's leaking before the one that fails                                                                                                                                                                                                                                                                                                                                  Feb 15, 2023    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-leaks`                             A pytest plugin to trace resource leaks.                                                                                                                                                                                                                                                                                                                                                Nov 27, 2019    1 - Planning           N/A
+   :pypi:`pytest-leaping`                           A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Mar 27, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-leo-interface`                     Pytest extension tool for leo projects.                                                                                                                                                                                                                                                                                                                                                 Mar 19, 2025    N/A                    N/A
+   :pypi:`pytest-level`                             Select tests of a given level or lower                                                                                                                                                                                                                                                                                                                                                  Oct 21, 2019    N/A                    pytest
+   :pypi:`pytest-libfaketime`                       A python-libfaketime plugin for pytest                                                                                                                                                                                                                                                                                                                                                  Apr 12, 2024    4 - Beta               pytest>=3.0.0
+   :pypi:`pytest-libiio`                            A pytest plugin to manage interfacing with libiio contexts                                                                                                                                                                                                                                                                                                                              Oct 01, 2024    4 - Beta               N/A
+   :pypi:`pytest-libnotify`                         Pytest plugin that shows notifications about the test run                                                                                                                                                                                                                                                                                                                               Apr 02, 2021    3 - Alpha              pytest
+   :pypi:`pytest-ligo`                                                                                                                                                                                                                                                                                                                                                                                                                      Jan 16, 2020    4 - Beta               N/A
+   :pypi:`pytest-lineno`                            A pytest plugin to show the line numbers of test functions                                                                                                                                                                                                                                                                                                                              Dec 04, 2020    N/A                    pytest
+   :pypi:`pytest-line-profiler`                     Profile code executed by pytest                                                                                                                                                                                                                                                                                                                                                         Aug 10, 2023    4 - Beta               pytest >=3.5.0
+   :pypi:`pytest-line-profiler-apn`                 Profile code executed by pytest                                                                                                                                                                                                                                                                                                                                                         Dec 05, 2022    N/A                    pytest (>=3.5.0)
+   :pypi:`pytest-lisa`                              Pytest plugin for organizing tests.                                                                                                                                                                                                                                                                                                                                                     Jan 21, 2021    3 - Alpha              pytest (>=6.1.2,<7.0.0)
+   :pypi:`pytest-listener`                          A simple network listener                                                                                                                                                                                                                                                                                                                                                               Nov 29, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-litf`                              A pytest plugin that stream output in LITF format                                                                                                                                                                                                                                                                                                                                       Jan 18, 2021    4 - Beta               pytest (>=3.1.1)
+   :pypi:`pytest-litter`                            Pytest plugin which verifies that tests do not modify file trees.                                                                                                                                                                                                                                                                                                                       Nov 23, 2023    4 - Beta               pytest >=6.1
+   :pypi:`pytest-live`                              Live results for pytest                                                                                                                                                                                                                                                                                                                                                                 Mar 08, 2020    N/A                    pytest
+   :pypi:`pytest-llmeval`                           A pytest plugin to evaluate/benchmark LLM prompts                                                                                                                                                                                                                                                                                                                                       Mar 19, 2025    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-local-badge`                       Generate local badges (shields) reporting your test suite status.                                                                                                                                                                                                                                                                                                                       Jan 15, 2023    N/A                    pytest (>=6.1.0)
+   :pypi:`pytest-localftpserver`                    A PyTest plugin which provides an FTP fixture for your tests                                                                                                                                                                                                                                                                                                                            May 19, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-localserver`                       pytest plugin to test server connections locally.                                                                                                                                                                                                                                                                                                                                       Oct 06, 2024    4 - Beta               N/A
+   :pypi:`pytest-localstack`                        Pytest plugin for AWS integration tests                                                                                                                                                                                                                                                                                                                                                 Jun 07, 2023    4 - Beta               pytest (>=6.0.0,<7.0.0)
+   :pypi:`pytest-lock`                              pytest-lock is a pytest plugin that allows you to "lock" the results of unit tests, storing them in a local cache. This is particularly useful for tests that are resource-intensive or don't need to be run every time. When the tests are run subsequently, pytest-lock will compare the current results with the locked results and issue a warning if there are any discrepancies.  Feb 03, 2024    N/A                    pytest (>=7.4.3,<8.0.0)
+   :pypi:`pytest-lockable`                          lockable resource plugin for pytest                                                                                                                                                                                                                                                                                                                                                     Jan 24, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-locker`                            Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed                                                                                                                                                                                                                                                             Dec 20, 2024    N/A                    pytest>=5.4
+   :pypi:`pytest-log`                               print log                                                                                                                                                                                                                                                                                                                                                                               Aug 15, 2021    N/A                    pytest (>=3.8)
+   :pypi:`pytest-logbook`                           py.test plugin to capture logbook log messages                                                                                                                                                                                                                                                                                                                                          Nov 23, 2015    5 - Production/Stable  pytest (>=2.8)
+   :pypi:`pytest-logdog`                            Pytest plugin to test logging                                                                                                                                                                                                                                                                                                                                                           Jun 15, 2021    1 - Planning           pytest (>=6.2.0)
+   :pypi:`pytest-logfest`                           Pytest plugin providing three logger fixtures with basic or full writing to log files                                                                                                                                                                                                                                                                                                   Jul 21, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-logger`                            Plugin configuring handlers for loggers from Python logging module.                                                                                                                                                                                                                                                                                                                     Mar 10, 2024    5 - Production/Stable  pytest (>=3.2)
+   :pypi:`pytest-logging`                           Configures logging and allows tweaking the log level with a py.test flag                                                                                                                                                                                                                                                                                                                Nov 04, 2015    4 - Beta               N/A
+   :pypi:`pytest-logging-end-to-end-test-tool`                                                                                                                                                                                                                                                                                                                                                                                              Sep 23, 2022    N/A                    pytest (>=7.1.2,<8.0.0)
+   :pypi:`pytest-logging-strict`                    pytest fixture logging configured from packaged YAML                                                                                                                                                                                                                                                                                                                                    Mar 23, 2025    3 - Alpha              pytest
+   :pypi:`pytest-logikal`                           Common testing environment                                                                                                                                                                                                                                                                                                                                                              Apr 02, 2025    5 - Production/Stable  pytest==8.3.5
+   :pypi:`pytest-log-report`                        Package for creating a pytest test run reprot                                                                                                                                                                                                                                                                                                                                           Dec 26, 2019    N/A                    N/A
+   :pypi:`pytest-logscanner`                        Pytest plugin for logscanner (A logger for python logging outputting to easily viewable (and filterable) html files. Good for people not grep savey, and color higlighting and quickly changing filters might even bye useful for commandline wizards.)                                                                                                                                 Sep 30, 2024    4 - Beta               pytest>=8.2.2
+   :pypi:`pytest-loguru`                            Pytest Loguru                                                                                                                                                                                                                                                                                                                                                                           Mar 20, 2024    5 - Production/Stable  pytest; extra == "test"
+   :pypi:`pytest-loop`                              pytest plugin for looping tests                                                                                                                                                                                                                                                                                                                                                         Oct 17, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-lsp`                               A pytest plugin for end-to-end testing of language servers                                                                                                                                                                                                                                                                                                                              Nov 23, 2024    3 - Alpha              pytest
+   :pypi:`pytest-lw-realtime-result`                Pytest plugin to generate realtime test results to a file                                                                                                                                                                                                                                                                                                                               Mar 13, 2025    N/A                    pytest>=3.5.0
+   :pypi:`pytest-manifest`                          PyTest plugin for recording and asserting against a manifest file                                                                                                                                                                                                                                                                                                                       Apr 01, 2025    N/A                    pytest
+   :pypi:`pytest-manual-marker`                     pytest marker for marking manual tests                                                                                                                                                                                                                                                                                                                                                  Aug 04, 2022    3 - Alpha              pytest>=7
+   :pypi:`pytest-mark-count`                        Get a count of the number of tests marked, unmarked, and unique tests if tests have multiple markers                                                                                                                                                                                                                                                                                    Nov 13, 2024    4 - Beta               pytest>=8.0.0
+   :pypi:`pytest-markdoctest`                       A pytest plugin to doctest your markdown files                                                                                                                                                                                                                                                                                                                                          Jul 22, 2022    4 - Beta               pytest (>=6)
+   :pypi:`pytest-markdown`                          Test your markdown docs with pytest                                                                                                                                                                                                                                                                                                                                                     Jan 15, 2021    4 - Beta               pytest (>=6.0.1,<7.0.0)
+   :pypi:`pytest-markdown-docs`                     Run markdown code fences through pytest                                                                                                                                                                                                                                                                                                                                                 Mar 13, 2025    N/A                    pytest>=7.0.0
+   :pypi:`pytest-marker-bugzilla`                   py.test bugzilla integration plugin, using markers                                                                                                                                                                                                                                                                                                                                      Apr 02, 2025    5 - Production/Stable  pytest>=2.2.4
+   :pypi:`pytest-markers-presence`                  A simple plugin to detect missed pytest tags and markers"                                                                                                                                                                                                                                                                                                                               Oct 30, 2024    4 - Beta               pytest>=6.0
+   :pypi:`pytest-markfiltration`                    UNKNOWN                                                                                                                                                                                                                                                                                                                                                                                 Nov 08, 2011    3 - Alpha              N/A
+   :pypi:`pytest-mark-manage`                       用例标签化管理                                                                                                                                                                                                                                                                                                                                                                          Aug 15, 2024    N/A                    pytest
+   :pypi:`pytest-mark-no-py3`                       pytest plugin and bowler codemod to help migrate tests to Python 3                                                                                                                                                                                                                                                                                                                      May 17, 2019    N/A                    pytest
+   :pypi:`pytest-marks`                             UNKNOWN                                                                                                                                                                                                                                                                                                                                                                                 Nov 23, 2012    3 - Alpha              N/A
+   :pypi:`pytest-mask-secrets`                      Pytest plugin to hide sensitive data in test reports                                                                                                                                                                                                                                                                                                                                    Jan 28, 2025    N/A                    N/A
+   :pypi:`pytest-matcher`                           Easy way to match captured \`pytest\` output against expectations stored in files                                                                                                                                                                                                                                                                                                       Aug 01, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-matchers`                          Matchers for pytest                                                                                                                                                                                                                                                                                                                                                                     Feb 11, 2025    N/A                    pytest<9.0,>=7.0
+   :pypi:`pytest-match-skip`                        Skip matching marks. Matches partial marks using wildcards.                                                                                                                                                                                                                                                                                                                             May 15, 2019    4 - Beta               pytest (>=4.4.1)
+   :pypi:`pytest-mat-report`                        this is report                                                                                                                                                                                                                                                                                                                                                                          Jan 20, 2021    N/A                    N/A
+   :pypi:`pytest-matrix`                            Provide tools for generating tests from combinations of fixtures.                                                                                                                                                                                                                                                                                                                       Jun 24, 2020    5 - Production/Stable  pytest (>=5.4.3,<6.0.0)
+   :pypi:`pytest-maxcov`                            Compute the maximum coverage available through pytest with the minimum execution time cost                                                                                                                                                                                                                                                                                              Sep 24, 2023    N/A                    pytest (>=7.4.0,<8.0.0)
+   :pypi:`pytest-max-warnings`                      A Pytest plugin to exit non-zero exit code when the configured maximum warnings has been exceeded.                                                                                                                                                                                                                                                                                      Oct 23, 2024    4 - Beta               pytest>=8.3.3
+   :pypi:`pytest-maybe-context`                     Simplify tests with warning and exception cases.                                                                                                                                                                                                                                                                                                                                        Apr 16, 2023    N/A                    pytest (>=7,<8)
+   :pypi:`pytest-maybe-raises`                      Pytest fixture for optional exception testing.                                                                                                                                                                                                                                                                                                                                          May 27, 2022    N/A                    pytest ; extra == 'dev'
+   :pypi:`pytest-mccabe`                            pytest plugin to run the mccabe code complexity checker.                                                                                                                                                                                                                                                                                                                                Jul 22, 2020    3 - Alpha              pytest (>=5.4.0)
+   :pypi:`pytest-md`                                Plugin for generating Markdown reports for pytest results                                                                                                                                                                                                                                                                                                                               Jul 11, 2019    3 - Alpha              pytest (>=4.2.1)
+   :pypi:`pytest-md-report`                         A pytest plugin to generate test outcomes reports with markdown table format.                                                                                                                                                                                                                                                                                                           Jan 02, 2025    4 - Beta               pytest!=6.0.0,<9,>=3.3.2
+   :pypi:`pytest-meilisearch`                       Pytest helpers for testing projects using Meilisearch                                                                                                                                                                                                                                                                                                                                   Oct 08, 2024    N/A                    pytest>=7.4.3
+   :pypi:`pytest-memlog`                            Log memory usage during tests                                                                                                                                                                                                                                                                                                                                                           May 03, 2023    N/A                    pytest (>=7.3.0,<8.0.0)
+   :pypi:`pytest-memprof`                           Estimates memory consumption of test functions                                                                                                                                                                                                                                                                                                                                          Mar 29, 2019    4 - Beta               N/A
+   :pypi:`pytest-memray`                            A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Jul 25, 2024    N/A                    pytest>=7.2
+   :pypi:`pytest-menu`                              A pytest plugin for console based interactive test selection just after the collection phase                                                                                                                                                                                                                                                                                            Oct 04, 2017    3 - Alpha              pytest (>=2.4.2)
+   :pypi:`pytest-mercurial`                         pytest plugin to write integration tests for projects using Mercurial Python internals                                                                                                                                                                                                                                                                                                  Nov 21, 2020    1 - Planning           N/A
+   :pypi:`pytest-mergify`                           Pytest plugin for Mergify                                                                                                                                                                                                                                                                                                                                                               Mar 31, 2025    N/A                    N/A
+   :pypi:`pytest-mesh`                              pytest_mesh插件                                                                                                                                                                                                                                                                                                                                                                         Aug 05, 2022    N/A                    pytest (==7.1.2)
+   :pypi:`pytest-message`                           Pytest plugin for sending report message of marked tests execution                                                                                                                                                                                                                                                                                                                      Aug 04, 2022    N/A                    pytest (>=6.2.5)
+   :pypi:`pytest-messenger`                         Pytest to Slack reporting plugin                                                                                                                                                                                                                                                                                                                                                        Nov 24, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-metadata`                          pytest plugin for test session metadata                                                                                                                                                                                                                                                                                                                                                 Feb 12, 2024    5 - Production/Stable  pytest>=7.0.0
+   :pypi:`pytest-metrics`                           Custom metrics report for pytest                                                                                                                                                                                                                                                                                                                                                        Apr 04, 2020    N/A                    pytest
+   :pypi:`pytest-mh`                                Pytest multihost plugin                                                                                                                                                                                                                                                                                                                                                                 Mar 06, 2025    N/A                    pytest
+   :pypi:`pytest-mimesis`                           Mimesis integration with the pytest test runner                                                                                                                                                                                                                                                                                                                                         Mar 21, 2020    5 - Production/Stable  pytest (>=4.2)
+   :pypi:`pytest-minecraft`                         A pytest plugin for running tests against Minecraft releases                                                                                                                                                                                                                                                                                                                            Apr 06, 2022    N/A                    pytest (>=6.0.1)
+   :pypi:`pytest-mini`                              A plugin to test mp                                                                                                                                                                                                                                                                                                                                                                     Feb 06, 2023    N/A                    pytest (>=7.2.0,<8.0.0)
+   :pypi:`pytest-minio-mock`                        A pytest plugin for mocking Minio S3 interactions                                                                                                                                                                                                                                                                                                                                       Aug 27, 2024    N/A                    pytest>=5.0.0
+   :pypi:`pytest-missing-fixtures`                  Pytest plugin that creates missing fixtures                                                                                                                                                                                                                                                                                                                                             Oct 14, 2020    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-missing-modules`                   Pytest plugin to easily fake missing modules                                                                                                                                                                                                                                                                                                                                            Sep 03, 2024    N/A                    pytest>=8.3.2
+   :pypi:`pytest-mitmproxy`                         pytest plugin for mitmproxy tests                                                                                                                                                                                                                                                                                                                                                       Nov 13, 2024    N/A                    pytest>=7.0
+   :pypi:`pytest-ml`                                Test your machine learning!                                                                                                                                                                                                                                                                                                                                                             May 04, 2019    4 - Beta               N/A
+   :pypi:`pytest-mocha`                             pytest plugin to display test execution output like a mochajs                                                                                                                                                                                                                                                                                                                           Apr 02, 2020    4 - Beta               pytest (>=5.4.0)
+   :pypi:`pytest-mock`                              Thin-wrapper around the mock package for easier use with pytest                                                                                                                                                                                                                                                                                                                         Mar 21, 2024    5 - Production/Stable  pytest>=6.2.5
+   :pypi:`pytest-mock-api`                          A mock API server with configurable routes and responses available as a fixture.                                                                                                                                                                                                                                                                                                        Feb 13, 2019    1 - Planning           pytest (>=4.0.0)
+   :pypi:`pytest-mock-generator`                    A pytest fixture wrapper for https://pypi.org/project/mock-generator                                                                                                                                                                                                                                                                                                                    May 16, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-mock-helper`                       Help you mock HTTP call and generate mock code                                                                                                                                                                                                                                                                                                                                          Jan 24, 2018    N/A                    pytest
+   :pypi:`pytest-mockito`                           Base fixtures for mockito                                                                                                                                                                                                                                                                                                                                                               Jul 11, 2018    4 - Beta               N/A
+   :pypi:`pytest-mockredis`                         An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database.                                                                                                                                                                                                                                                    Jan 02, 2018    2 - Pre-Alpha          N/A
+   :pypi:`pytest-mock-resources`                    A pytest plugin for easily instantiating reproducible mock resources.                                                                                                                                                                                                                                                                                                                   Mar 10, 2025    N/A                    pytest>=1.0
+   :pypi:`pytest-mock-server`                       Mock server plugin for pytest                                                                                                                                                                                                                                                                                                                                                           Jan 09, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-mockservers`                       A set of fixtures to test your requests to HTTP/UDP servers                                                                                                                                                                                                                                                                                                                             Mar 31, 2020    N/A                    pytest (>=4.3.0)
+   :pypi:`pytest-mocktcp`                           A pytest plugin for testing TCP clients                                                                                                                                                                                                                                                                                                                                                 Oct 11, 2022    N/A                    pytest
+   :pypi:`pytest-modalt`                            Massively distributed pytest runs using modal.com                                                                                                                                                                                                                                                                                                                                       Feb 27, 2024    4 - Beta               pytest >=6.2.0
+   :pypi:`pytest-modified-env`                      Pytest plugin to fail a test if it leaves modified \`os.environ\` afterwards.                                                                                                                                                                                                                                                                                                           Jan 29, 2022    4 - Beta               N/A
+   :pypi:`pytest-modifyjunit`                       Utility for adding additional properties to junit xml for IDM QE                                                                                                                                                                                                                                                                                                                        Jan 10, 2019    N/A                    N/A
+   :pypi:`pytest-molecule`                          PyTest Molecule Plugin :: discover and run molecule tests                                                                                                                                                                                                                                                                                                                               Mar 29, 2022    5 - Production/Stable  pytest (>=7.0.0)
+   :pypi:`pytest-molecule-JC`                       PyTest Molecule Plugin :: discover and run molecule tests                                                                                                                                                                                                                                                                                                                               Jul 18, 2023    5 - Production/Stable  pytest (>=7.0.0)
+   :pypi:`pytest-mongo`                             MongoDB process and client fixtures plugin for Pytest.                                                                                                                                                                                                                                                                                                                                  Feb 28, 2025    5 - Production/Stable  pytest>=6.2
+   :pypi:`pytest-mongodb`                           pytest plugin for MongoDB fixtures                                                                                                                                                                                                                                                                                                                                                      May 16, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-mongodb-nono`                      pytest plugin for MongoDB                                                                                                                                                                                                                                                                                                                                                               Jan 07, 2025    N/A                    N/A
+   :pypi:`pytest-mongodb-ry`                        pytest plugin for MongoDB                                                                                                                                                                                                                                                                                                                                                               Jan 21, 2025    N/A                    N/A
+   :pypi:`pytest-monitor`                           Pytest plugin for analyzing resource usage.                                                                                                                                                                                                                                                                                                                                             Jun 25, 2023    5 - Production/Stable  pytest
+   :pypi:`pytest-monkeyplus`                        pytest's monkeypatch subclass with extra functionalities                                                                                                                                                                                                                                                                                                                                Sep 18, 2012    5 - Production/Stable  N/A
+   :pypi:`pytest-monkeytype`                        pytest-monkeytype: Generate Monkeytype annotations from your pytest tests.                                                                                                                                                                                                                                                                                                              Jul 29, 2020    4 - Beta               N/A
+   :pypi:`pytest-moto`                              Fixtures for integration tests of AWS services,uses moto mocking library.                                                                                                                                                                                                                                                                                                               Aug 28, 2015    1 - Planning           N/A
+   :pypi:`pytest-moto-fixtures`                     Fixtures for testing code that interacts with AWS                                                                                                                                                                                                                                                                                                                                       Feb 04, 2025    1 - Planning           pytest<9,>=8.3; extra == "pytest"
+   :pypi:`pytest-motor`                             A pytest plugin for motor, the non-blocking MongoDB driver.                                                                                                                                                                                                                                                                                                                             Jul 21, 2021    3 - Alpha              pytest
+   :pypi:`pytest-mp`                                A test batcher for multiprocessed Pytest runs                                                                                                                                                                                                                                                                                                                                           May 23, 2018    4 - Beta               pytest
+   :pypi:`pytest-mpi`                               pytest plugin to collect information from tests                                                                                                                                                                                                                                                                                                                                         Jan 08, 2022    3 - Alpha              pytest
+   :pypi:`pytest-mpiexec`                           pytest plugin for running individual tests with mpiexec                                                                                                                                                                                                                                                                                                                                 Jul 29, 2024    3 - Alpha              pytest
+   :pypi:`pytest-mpl`                               pytest plugin to help with testing figures output from Matplotlib                                                                                                                                                                                                                                                                                                                       Feb 14, 2024    4 - Beta               pytest
+   :pypi:`pytest-mproc`                             low-startup-overhead, scalable, distributed-testing pytest plugin                                                                                                                                                                                                                                                                                                                       Nov 15, 2022    4 - Beta               pytest (>=6)
+   :pypi:`pytest-mqtt`                              pytest-mqtt supports testing systems based on MQTT                                                                                                                                                                                                                                                                                                                                      Jan 07, 2025    5 - Production/Stable  pytest<9; extra == "test"
+   :pypi:`pytest-multihost`                         Utility for writing multi-host tests for pytest                                                                                                                                                                                                                                                                                                                                         Apr 07, 2020    4 - Beta               N/A
+   :pypi:`pytest-multilog`                          Multi-process logs handling and other helpers for pytest                                                                                                                                                                                                                                                                                                                                Jan 17, 2023    N/A                    pytest
+   :pypi:`pytest-multithreading`                    a pytest plugin for th and concurrent testing                                                                                                                                                                                                                                                                                                                                           Aug 05, 2024    N/A                    N/A
+   :pypi:`pytest-multithreading-allure`             pytest_multithreading_allure                                                                                                                                                                                                                                                                                                                                                            Nov 25, 2022    N/A                    N/A
+   :pypi:`pytest-mutagen`                           Add the mutation testing feature to pytest                                                                                                                                                                                                                                                                                                                                              Jul 24, 2020    N/A                    pytest (>=5.4)
+   :pypi:`pytest-my-cool-lib`                                                                                                                                                                                                                                                                                                                                                                                                               Nov 02, 2023    N/A                    pytest (>=7.1.3,<8.0.0)
+   :pypi:`pytest-my-plugin`                         A pytest plugin that does awesome things                                                                                                                                                                                                                                                                                                                                                Jan 27, 2025    N/A                    pytest>=6.0
+   :pypi:`pytest-mypy`                              A Pytest Plugin for Mypy                                                                                                                                                                                                                                                                                                                                                                Apr 02, 2025    5 - Production/Stable  pytest>=7.0
+   :pypi:`pytest-mypyd`                             Mypy static type checker plugin for Pytest                                                                                                                                                                                                                                                                                                                                              Aug 20, 2019    4 - Beta               pytest (<4.7,>=2.8) ; python_version < "3.5"
+   :pypi:`pytest-mypy-plugins`                      pytest plugin for writing tests for mypy plugins                                                                                                                                                                                                                                                                                                                                        Dec 21, 2024    4 - Beta               pytest>=7.0.0
+   :pypi:`pytest-mypy-plugins-shim`                 Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy.                                                                                                                                                                                                                                                                                         Feb 14, 2025    N/A                    pytest>=6.0.0
+   :pypi:`pytest-mypy-runner`                       Run the mypy static type checker as a pytest test case                                                                                                                                                                                                                                                                                                                                  Apr 23, 2024    N/A                    pytest>=8.0
+   :pypi:`pytest-mypy-testing`                      Pytest plugin to check mypy output.                                                                                                                                                                                                                                                                                                                                                     Mar 04, 2024    N/A                    pytest>=7,<9
+   :pypi:`pytest-mysql`                             MySQL process and client fixtures for pytest                                                                                                                                                                                                                                                                                                                                            Dec 10, 2024    5 - Production/Stable  pytest>=6.2
+   :pypi:`pytest-ndb`                               pytest notebook debugger                                                                                                                                                                                                                                                                                                                                                                Apr 28, 2024    N/A                    pytest
+   :pypi:`pytest-needle`                            pytest plugin for visual testing websites using selenium                                                                                                                                                                                                                                                                                                                                Dec 10, 2018    4 - Beta               pytest (<5.0.0,>=3.0.0)
+   :pypi:`pytest-neo`                               pytest-neo is a plugin for pytest that shows tests like screen of Matrix.                                                                                                                                                                                                                                                                                                               Jan 08, 2022    3 - Alpha              pytest (>=6.2.0)
+   :pypi:`pytest-neos`                              Pytest plugin for neos                                                                                                                                                                                                                                                                                                                                                                  Sep 10, 2024    5 - Production/Stable  pytest<8.0,>=7.2; extra == "dev"
+   :pypi:`pytest-netconf`                           A pytest plugin that provides a mock NETCONF (RFC6241/RFC6242) server for local testing.                                                                                                                                                                                                                                                                                                Jan 06, 2025    N/A                    N/A
+   :pypi:`pytest-netdut`                            "Automated software testing for switches using pytest"                                                                                                                                                                                                                                                                                                                                  Jul 05, 2024    N/A                    pytest<7.3,>=3.5.0
+   :pypi:`pytest-network`                           A simple plugin to disable network on socket level.                                                                                                                                                                                                                                                                                                                                     May 07, 2020    N/A                    N/A
+   :pypi:`pytest-network-endpoints`                 Network endpoints plugin for pytest                                                                                                                                                                                                                                                                                                                                                     Mar 06, 2022    N/A                    pytest
+   :pypi:`pytest-never-sleep`                       pytest plugin helps to avoid adding tests without mock \`time.sleep\`                                                                                                                                                                                                                                                                                                                   May 05, 2021    3 - Alpha              pytest (>=3.5.1)
+   :pypi:`pytest-nginx`                             nginx fixture for pytest                                                                                                                                                                                                                                                                                                                                                                Aug 12, 2017    5 - Production/Stable  N/A
+   :pypi:`pytest-nginx-iplweb`                      nginx fixture for pytest - iplweb temporary fork                                                                                                                                                                                                                                                                                                                                        Mar 01, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-ngrok`                                                                                                                                                                                                                                                                                                                                                                                                                     Jan 20, 2022    3 - Alpha              pytest
+   :pypi:`pytest-ngsfixtures`                       pytest ngs fixtures                                                                                                                                                                                                                                                                                                                                                                     Sep 06, 2019    2 - Pre-Alpha          pytest (>=5.0.0)
+   :pypi:`pytest-nhsd-apim`                         Pytest plugin accessing NHSDigital's APIM proxies                                                                                                                                                                                                                                                                                                                                       Apr 01, 2025    N/A                    pytest<9.0.0,>=8.2.0
+   :pypi:`pytest-nice`                              A pytest plugin that alerts user of failed test cases with screen notifications                                                                                                                                                                                                                                                                                                         May 04, 2019    4 - Beta               pytest
+   :pypi:`pytest-nice-parametrize`                  A small snippet for nicer PyTest's Parametrize                                                                                                                                                                                                                                                                                                                                          Apr 17, 2021    5 - Production/Stable  N/A
+   :pypi:`pytest_nlcov`                             Pytest plugin to get the coverage of the new lines (based on git diff) only                                                                                                                                                                                                                                                                                                             Aug 05, 2024    N/A                    N/A
+   :pypi:`pytest-nocustom`                          Run all tests without custom markers                                                                                                                                                                                                                                                                                                                                                    Aug 05, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-node-dependency`                   pytest plugin for controlling execution flow                                                                                                                                                                                                                                                                                                                                            Apr 10, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-nodev`                             Test-driven source code search for Python.                                                                                                                                                                                                                                                                                                                                              Jul 21, 2016    4 - Beta               pytest (>=2.8.1)
+   :pypi:`pytest-nogarbage`                         Ensure a test produces no garbage                                                                                                                                                                                                                                                                                                                                                       Feb 24, 2025    5 - Production/Stable  pytest>=4.6.0
+   :pypi:`pytest-nose-attrib`                       pytest plugin to use nose @attrib marks decorators and pick tests based on attributes and partially uses nose-attrib plugin approach                                                                                                                                                                                                                                                    Aug 13, 2023    N/A                    N/A
+   :pypi:`pytest_notebook`                          A pytest plugin for testing Jupyter Notebooks.                                                                                                                                                                                                                                                                                                                                          Nov 28, 2023    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-notice`                            Send pytest execution result email                                                                                                                                                                                                                                                                                                                                                      Nov 05, 2020    N/A                    N/A
+   :pypi:`pytest-notification`                      A pytest plugin for sending a desktop notification and playing a sound upon completion of tests                                                                                                                                                                                                                                                                                         Jun 19, 2020    N/A                    pytest (>=4)
+   :pypi:`pytest-notifier`                          A pytest plugin to notify test result                                                                                                                                                                                                                                                                                                                                                   Jun 12, 2020    3 - Alpha              pytest
+   :pypi:`pytest_notify`                            Get notifications when your tests ends                                                                                                                                                                                                                                                                                                                                                  Jul 05, 2017    N/A                    pytest>=3.0.0
+   :pypi:`pytest-notimplemented`                    Pytest markers for not implemented features and tests.                                                                                                                                                                                                                                                                                                                                  Aug 27, 2019    N/A                    pytest (>=5.1,<6.0)
+   :pypi:`pytest-notion`                            A PyTest Reporter to send test runs to Notion.so                                                                                                                                                                                                                                                                                                                                        Aug 07, 2019    N/A                    N/A
+   :pypi:`pytest-nunit`                             A pytest plugin for generating NUnit3 test result XML output                                                                                                                                                                                                                                                                                                                            Feb 26, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-oar`                               PyTest plugin for the OAR testing framework                                                                                                                                                                                                                                                                                                                                             May 02, 2023    N/A                    pytest>=6.0.1
+   :pypi:`pytest-oarepo`                                                                                                                                                                                                                                                                                                                                                                                                                    Feb 14, 2025    N/A                    pytest>=7.1.2; extra == "base"
+   :pypi:`pytest-object-getter`                     Import any object from a 3rd party module while mocking its namespace on demand.                                                                                                                                                                                                                                                                                                        Jul 31, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest-ochrus`                            pytest results data-base and HTML reporter                                                                                                                                                                                                                                                                                                                                              Feb 21, 2018    4 - Beta               N/A
+   :pypi:`pytest-odc`                               A pytest plugin for simplifying ODC database tests                                                                                                                                                                                                                                                                                                                                      Aug 04, 2023    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-odoo`                              py.test plugin to run Odoo tests                                                                                                                                                                                                                                                                                                                                                        Mar 12, 2025    5 - Production/Stable  pytest>=8
+   :pypi:`pytest-odoo-fixtures`                     Project description                                                                                                                                                                                                                                                                                                                                                                     Jun 25, 2019    N/A                    N/A
+   :pypi:`pytest-oerp`                              pytest plugin to test OpenERP modules                                                                                                                                                                                                                                                                                                                                                   Feb 28, 2012    3 - Alpha              N/A
+   :pypi:`pytest-offline`                                                                                                                                                                                                                                                                                                                                                                                                                   Mar 09, 2023    1 - Planning           pytest (>=7.0.0,<8.0.0)
+   :pypi:`pytest-ogsm-plugin`                       针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数                                                                                                                                                                                                                                                                                                               May 16, 2023    N/A                    N/A
+   :pypi:`pytest-ok`                                The ultimate pytest output plugin                                                                                                                                                                                                                                                                                                                                                       Apr 01, 2019    4 - Beta               N/A
+   :pypi:`pytest-only`                              Use @pytest.mark.only to run a single test                                                                                                                                                                                                                                                                                                                                              May 27, 2024    5 - Production/Stable  pytest<9,>=3.6.0
+   :pypi:`pytest-oof`                               A Pytest plugin providing structured, programmatic access to a test run's results                                                                                                                                                                                                                                                                                                       Dec 11, 2023    4 - Beta               N/A
+   :pypi:`pytest-oot`                               Run object-oriented tests in a simple format                                                                                                                                                                                                                                                                                                                                            Sep 18, 2016    4 - Beta               N/A
+   :pypi:`pytest-openfiles`                         Pytest plugin for detecting inadvertent open file handles                                                                                                                                                                                                                                                                                                                               Jun 05, 2024    3 - Alpha              pytest>=4.6
+   :pypi:`pytest-open-html`                         Auto-open HTML reports after pytest runs                                                                                                                                                                                                                                                                                                                                                Mar 31, 2025    N/A                    pytest>=6.0
+   :pypi:`pytest-opentelemetry`                     A pytest plugin for instrumenting test runs via OpenTelemetry                                                                                                                                                                                                                                                                                                                           Nov 22, 2024    N/A                    pytest
+   :pypi:`pytest-opentmi`                           pytest plugin for publish results to opentmi                                                                                                                                                                                                                                                                                                                                            Mar 22, 2025    5 - Production/Stable  pytest>=5.0
+   :pypi:`pytest-operator`                          Fixtures for Operators                                                                                                                                                                                                                                                                                                                                                                  Sep 28, 2022    N/A                    pytest
+   :pypi:`pytest-optional`                          include/exclude values of fixtures in pytest                                                                                                                                                                                                                                                                                                                                            Oct 07, 2015    N/A                    N/A
+   :pypi:`pytest-optional-tests`                    Easy declaration of optional tests (i.e., that are not run by default)                                                                                                                                                                                                                                                                                                                  Jul 09, 2019    4 - Beta               pytest (>=4.5.0)
+   :pypi:`pytest-orchestration`                     A pytest plugin for orchestrating tests                                                                                                                                                                                                                                                                                                                                                 Jul 18, 2019    N/A                    N/A
+   :pypi:`pytest-order`                             pytest plugin to run your tests in a specific order                                                                                                                                                                                                                                                                                                                                     Aug 22, 2024    5 - Production/Stable  pytest>=5.0; python_version < "3.10"
+   :pypi:`pytest-ordered`                           Declare the order in which tests should run in your pytest.ini                                                                                                                                                                                                                                                                                                                          Oct 07, 2024    N/A                    pytest>=6.2.0
+   :pypi:`pytest-ordering`                          pytest plugin to run your tests in a specific order                                                                                                                                                                                                                                                                                                                                     Nov 14, 2018    4 - Beta               pytest
+   :pypi:`pytest-order-modify`                      新增run_marker 来自定义用例的执行顺序                                                                                                                                                                                                                                                                                                                                                   Nov 04, 2022    N/A                    N/A
+   :pypi:`pytest-osxnotify`                         OS X notifications for py.test results.                                                                                                                                                                                                                                                                                                                                                 May 15, 2015    N/A                    N/A
+   :pypi:`pytest-ot`                                A pytest plugin for instrumenting test runs via OpenTelemetry                                                                                                                                                                                                                                                                                                                           Mar 21, 2024    N/A                    pytest; extra == "dev"
+   :pypi:`pytest-otel`                              OpenTelemetry plugin for Pytest                                                                                                                                                                                                                                                                                                                                                         Feb 10, 2025    N/A                    pytest==8.3.4
+   :pypi:`pytest-override-env-var`                  Pytest mark to override a value of an environment variable.                                                                                                                                                                                                                                                                                                                             Feb 25, 2023    N/A                    N/A
+   :pypi:`pytest-owner`                             Add owner mark for tests                                                                                                                                                                                                                                                                                                                                                                Aug 19, 2024    N/A                    pytest
+   :pypi:`pytest-pact`                              A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Jan 07, 2019    4 - Beta               N/A
+   :pypi:`pytest-pagerduty`                         Pytest plugin for PagerDuty integration via automation testing.                                                                                                                                                                                                                                                                                                                         Mar 22, 2025    N/A                    pytest<9.0.0,>=7.4.0
+   :pypi:`pytest-pahrametahrize`                    Parametrize your tests with a Boston accent.                                                                                                                                                                                                                                                                                                                                            Nov 24, 2021    4 - Beta               pytest (>=6.0,<7.0)
+   :pypi:`pytest-parallel`                          a pytest plugin for parallel and concurrent testing                                                                                                                                                                                                                                                                                                                                     Oct 10, 2021    3 - Alpha              pytest (>=3.0.0)
+   :pypi:`pytest-parallel-39`                       a pytest plugin for parallel and concurrent testing                                                                                                                                                                                                                                                                                                                                     Jul 12, 2021    3 - Alpha              pytest (>=3.0.0)
+   :pypi:`pytest-parallelize-tests`                 pytest plugin that parallelizes test execution across multiple hosts                                                                                                                                                                                                                                                                                                                    Jan 27, 2023    4 - Beta               N/A
+   :pypi:`pytest-param`                             pytest plugin to test all, first, last or random params                                                                                                                                                                                                                                                                                                                                 Sep 11, 2016    4 - Beta               pytest (>=2.6.0)
+   :pypi:`pytest-paramark`                          Configure pytest fixtures using a combination of"parametrize" and markers                                                                                                                                                                                                                                                                                                               Jan 10, 2020    4 - Beta               pytest (>=4.5.0)
+   :pypi:`pytest-parametrization`                   Simpler PyTest parametrization                                                                                                                                                                                                                                                                                                                                                          May 22, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-parametrization-annotation`        A pytest library for parametrizing tests using type hints.                                                                                                                                                                                                                                                                                                                              Dec 10, 2024    5 - Production/Stable  pytest>=7
+   :pypi:`pytest-parametrize`                       pytest decorator for parametrizing test cases in a dict-way                                                                                                                                                                                                                                                                                                                             Nov 10, 2024    5 - Production/Stable  pytest<9.0.0,>=8.3.0
+   :pypi:`pytest-parametrize-cases`                 A more user-friendly way to write parametrized tests.                                                                                                                                                                                                                                                                                                                                   Mar 13, 2022    N/A                    pytest (>=6.1.2)
+   :pypi:`pytest-parametrized`                      Pytest decorator for parametrizing tests with default iterables.                                                                                                                                                                                                                                                                                                                        Dec 21, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-parametrize-suite`                 A simple pytest extension for creating a named test suite.                                                                                                                                                                                                                                                                                                                              Jan 19, 2023    5 - Production/Stable  pytest
+   :pypi:`pytest_param_files`                       Create pytest parametrize decorators from external files.                                                                                                                                                                                                                                                                                                                               Jul 29, 2023    N/A                    pytest
+   :pypi:`pytest-params`                            Simplified pytest test case parameters.                                                                                                                                                                                                                                                                                                                                                 Aug 05, 2024    N/A                    pytest>=7.0.0
+   :pypi:`pytest-param-scope`                       pytest parametrize scope fixture workaround                                                                                                                                                                                                                                                                                                                                             Oct 18, 2023    N/A                    pytest
+   :pypi:`pytest-parawtf`                           Finally spell paramete?ri[sz]e correctly                                                                                                                                                                                                                                                                                                                                                Dec 03, 2018    4 - Beta               pytest (>=3.6.0)
+   :pypi:`pytest-pass`                              Check out https://github.com/elilutsky/pytest-pass                                                                                                                                                                                                                                                                                                                                      Dec 04, 2019    N/A                    N/A
+   :pypi:`pytest-passrunner`                        Pytest plugin providing the 'run_on_pass' marker                                                                                                                                                                                                                                                                                                                                        Feb 10, 2021    5 - Production/Stable  pytest (>=4.6.0)
+   :pypi:`pytest-paste-config`                      Allow setting the path to a paste config file                                                                                                                                                                                                                                                                                                                                           Sep 18, 2013    3 - Alpha              N/A
+   :pypi:`pytest-patch`                             An automagic \`patch\` fixture that can patch objects directly or by name.                                                                                                                                                                                                                                                                                                              Apr 29, 2023    3 - Alpha              pytest (>=7.0.0)
+   :pypi:`pytest-patches`                           A contextmanager pytest fixture for handling multiple mock patches                                                                                                                                                                                                                                                                                                                      Aug 30, 2021    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-patterns`                          pytest plugin to make testing complicated long string output easy to write and easy to debug                                                                                                                                                                                                                                                                                            Oct 22, 2024    4 - Beta               pytest>=6
+   :pypi:`pytest-pdb`                               pytest plugin which adds pdb helper commands related to pytest.                                                                                                                                                                                                                                                                                                                         Jul 31, 2018    N/A                    N/A
+   :pypi:`pytest-peach`                             pytest plugin for fuzzing with Peach API Security                                                                                                                                                                                                                                                                                                                                       Apr 12, 2019    4 - Beta               pytest (>=2.8.7)
+   :pypi:`pytest-pep257`                            py.test plugin for pep257                                                                                                                                                                                                                                                                                                                                                               Jul 09, 2016    N/A                    N/A
+   :pypi:`pytest-pep8`                              pytest plugin to check PEP8 requirements                                                                                                                                                                                                                                                                                                                                                Apr 27, 2014    N/A                    N/A
+   :pypi:`pytest-percent`                           Change the exit code of pytest test sessions when a required percent of tests pass.                                                                                                                                                                                                                                                                                                     May 21, 2020    N/A                    pytest (>=5.2.0)
+   :pypi:`pytest-percents`                                                                                                                                                                                                                                                                                                                                                                                                                  Mar 16, 2024    N/A                    N/A
+   :pypi:`pytest-perf`                              Run performance tests against the mainline code.                                                                                                                                                                                                                                                                                                                                        May 20, 2024    5 - Production/Stable  pytest!=8.1.*,>=6; extra == "testing"
+   :pypi:`pytest-performance`                       A simple plugin to ensure the execution of critical sections of code has not been impacted                                                                                                                                                                                                                                                                                              Sep 11, 2020    5 - Production/Stable  pytest (>=3.7.0)
+   :pypi:`pytest-performancetotal`                  A performance plugin for pytest                                                                                                                                                                                                                                                                                                                                                         Feb 01, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-persistence`                       Pytest tool for persistent objects                                                                                                                                                                                                                                                                                                                                                      Aug 21, 2024    N/A                    N/A
+   :pypi:`pytest-pexpect`                           Pytest pexpect plugin.                                                                                                                                                                                                                                                                                                                                                                  Aug 13, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-pg`                                A tiny plugin for pytest which runs PostgreSQL in Docker                                                                                                                                                                                                                                                                                                                                Apr 03, 2025    5 - Production/Stable  pytest>=8.0.0
+   :pypi:`pytest-pgsql`                             Pytest plugins and helpers for tests using a Postgres database.                                                                                                                                                                                                                                                                                                                         May 13, 2020    5 - Production/Stable  pytest (>=3.0.0)
+   :pypi:`pytest-phmdoctest`                        pytest plugin to test Python examples in Markdown using phmdoctest.                                                                                                                                                                                                                                                                                                                     Apr 15, 2022    4 - Beta               pytest (>=5.4.3)
+   :pypi:`pytest-phoenix-interface`                 Pytest extension tool for phoenix projects.                                                                                                                                                                                                                                                                                                                                             Mar 19, 2025    N/A                    N/A
+   :pypi:`pytest-picked`                            Run the tests related to the changed files                                                                                                                                                                                                                                                                                                                                              Nov 06, 2024    N/A                    pytest>=3.7.0
+   :pypi:`pytest-pickle-cache`                      A pytest plugin for caching test results using pickle.                                                                                                                                                                                                                                                                                                                                  Feb 17, 2025    N/A                    pytest>=7
+   :pypi:`pytest-pigeonhole`                                                                                                                                                                                                                                                                                                                                                                                                                Jun 25, 2018    5 - Production/Stable  pytest (>=3.4)
+   :pypi:`pytest-pikachu`                           Show surprise when tests are passing                                                                                                                                                                                                                                                                                                                                                    Aug 05, 2021    5 - Production/Stable  pytest
+   :pypi:`pytest-pilot`                             Slice in your test base thanks to powerful markers.                                                                                                                                                                                                                                                                                                                                     Oct 09, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-pingguo-pytest-plugin`             pingguo test                                                                                                                                                                                                                                                                                                                                                                            Oct 26, 2022    4 - Beta               N/A
+   :pypi:`pytest-pings`                             🦊 The pytest plugin for Firefox Telemetry 📊                                                                                                                                                                                                                                                                                                                                           Jun 29, 2019    3 - Alpha              pytest (>=5.0.0)
+   :pypi:`pytest-pinned`                            A simple pytest plugin for pinning tests                                                                                                                                                                                                                                                                                                                                                Sep 17, 2021    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-pinpoint`                          A pytest plugin which runs SBFL algorithms to detect faults.                                                                                                                                                                                                                                                                                                                            Sep 25, 2020    N/A                    pytest (>=4.4.0)
+   :pypi:`pytest-pipeline`                          Pytest plugin for functional testing of data analysispipelines                                                                                                                                                                                                                                                                                                                          Jan 24, 2017    3 - Alpha              N/A
+   :pypi:`pytest-pitch`                             runs tests in an order such that coverage increases as fast as possible                                                                                                                                                                                                                                                                                                                 Nov 02, 2023    4 - Beta               pytest >=7.3.1
+   :pypi:`pytest-platform-adapter`                  Pytest集成自动化平台插件                                                                                                                                                                                                                                                                                                                                                                Feb 18, 2025    5 - Production/Stable  pytest>=6.2.5
+   :pypi:`pytest-platform-markers`                  Markers for pytest to skip tests on specific platforms                                                                                                                                                                                                                                                                                                                                  Sep 09, 2019    4 - Beta               pytest (>=3.6.0)
+   :pypi:`pytest-play`                              pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files                                                                                                                                                                                                                                                                       Jun 12, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-playbook`                          Pytest plugin for reading playbooks.                                                                                                                                                                                                                                                                                                                                                    Jan 21, 2021    3 - Alpha              pytest (>=6.1.2,<7.0.0)
+   :pypi:`pytest-playwright`                        A pytest wrapper with fixtures for Playwright to automate web browsers                                                                                                                                                                                                                                                                                                                  Jan 31, 2025    N/A                    pytest<9.0.0,>=6.2.4
+   :pypi:`pytest_playwright_async`                  ASYNC Pytest plugin for Playwright                                                                                                                                                                                                                                                                                                                                                      Sep 28, 2024    N/A                    N/A
+   :pypi:`pytest-playwright-asyncio`                A pytest wrapper with async fixtures for Playwright to automate web browsers                                                                                                                                                                                                                                                                                                            Jan 31, 2025    N/A                    pytest<9.0.0,>=6.2.4
+   :pypi:`pytest-playwright-axe`                    An axe-core integration for accessibility testing using Playwright Python.                                                                                                                                                                                                                                                                                                              Mar 27, 2025    4 - Beta               N/A
+   :pypi:`pytest-playwright-enhanced`               A pytest plugin for playwright python                                                                                                                                                                                                                                                                                                                                                   Mar 24, 2024    N/A                    pytest<9.0.0,>=8.0.0
+   :pypi:`pytest-playwrights`                       A pytest wrapper with fixtures for Playwright to automate web browsers                                                                                                                                                                                                                                                                                                                  Dec 02, 2021    N/A                    N/A
+   :pypi:`pytest-playwright-snapshot`               A pytest wrapper for snapshot testing with playwright                                                                                                                                                                                                                                                                                                                                   Aug 19, 2021    N/A                    N/A
+   :pypi:`pytest-playwright-visual`                 A pytest fixture for visual testing with Playwright                                                                                                                                                                                                                                                                                                                                     Apr 28, 2022    N/A                    N/A
+   :pypi:`pytest-playwright-visual-snapshot`        Easy pytest visual regression testing using playwright                                                                                                                                                                                                                                                                                                                                  Mar 25, 2025    N/A                    N/A
+   :pypi:`pytest-plone`                             Pytest plugin to test Plone addons                                                                                                                                                                                                                                                                                                                                                      Mar 27, 2025    3 - Alpha              pytest<8.0.0
+   :pypi:`pytest-plt`                               Fixtures for quickly making Matplotlib plots in tests                                                                                                                                                                                                                                                                                                                                   Jan 17, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-plugin-helpers`                    A plugin to help developing and testing other plugins                                                                                                                                                                                                                                                                                                                                   Nov 23, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-plus`                              PyTest Plus Plugin :: extends pytest functionality                                                                                                                                                                                                                                                                                                                                      Feb 02, 2025    5 - Production/Stable  pytest>=7.4.2
+   :pypi:`pytest-pmisc`                                                                                                                                                                                                                                                                                                                                                                                                                     Mar 21, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-pogo`                              Pytest plugin for pogo-migrate                                                                                                                                                                                                                                                                                                                                                          Sep 09, 2024    4 - Beta               pytest<9,>=7
+   :pypi:`pytest-pointers`                          Pytest plugin to define functions you test with special marks for better navigation and reports                                                                                                                                                                                                                                                                                         Dec 26, 2022    N/A                    N/A
+   :pypi:`pytest-pokie`                             Pokie plugin for pytest                                                                                                                                                                                                                                                                                                                                                                 Oct 19, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-polarion-cfme`                     pytest plugin for collecting test cases and recording test results                                                                                                                                                                                                                                                                                                                      Nov 13, 2017    3 - Alpha              N/A
+   :pypi:`pytest-polarion-collect`                  pytest plugin for collecting polarion test cases data                                                                                                                                                                                                                                                                                                                                   Jun 18, 2020    3 - Alpha              pytest
+   :pypi:`pytest-polecat`                           Provides Polecat pytest fixtures                                                                                                                                                                                                                                                                                                                                                        Aug 12, 2019    4 - Beta               N/A
+   :pypi:`pytest-ponyorm`                           PonyORM in Pytest                                                                                                                                                                                                                                                                                                                                                                       Oct 31, 2018    N/A                    pytest (>=3.1.1)
+   :pypi:`pytest-poo`                               Visualize your crappy tests                                                                                                                                                                                                                                                                                                                                                             Mar 25, 2021    5 - Production/Stable  pytest (>=2.3.4)
+   :pypi:`pytest-poo-fail`                          Visualize your failed tests with poo                                                                                                                                                                                                                                                                                                                                                    Feb 12, 2015    5 - Production/Stable  N/A
+   :pypi:`pytest-pook`                              Pytest plugin for pook                                                                                                                                                                                                                                                                                                                                                                  Feb 15, 2024    4 - Beta               pytest
+   :pypi:`pytest-pop`                               A pytest plugin to help with testing pop projects                                                                                                                                                                                                                                                                                                                                       May 09, 2023    5 - Production/Stable  pytest
+   :pypi:`pytest-porcochu`                          Show surprise when tests are passing                                                                                                                                                                                                                                                                                                                                                    Nov 28, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-portion`                           Select a portion of the collected tests                                                                                                                                                                                                                                                                                                                                                 Jan 28, 2021    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-postgres`                          Run PostgreSQL in Docker container in Pytest.                                                                                                                                                                                                                                                                                                                                           Mar 22, 2020    N/A                    pytest
+   :pypi:`pytest-postgresql`                        Postgresql fixtures and fixture factories for Pytest.                                                                                                                                                                                                                                                                                                                                   Mar 19, 2025    5 - Production/Stable  pytest>=6.2
+   :pypi:`pytest-power`                             pytest plugin with powerful fixtures                                                                                                                                                                                                                                                                                                                                                    Dec 31, 2020    N/A                    pytest (>=5.4)
+   :pypi:`pytest-powerpack`                         A plugin containing extra batteries for pytest                                                                                                                                                                                                                                                                                                                                          Jan 04, 2025    N/A                    pytest<9.0.0,>=8.1.1
+   :pypi:`pytest-prefer-nested-dup-tests`           A Pytest plugin to drop duplicated tests during collection, but will prefer keeping nested packages.                                                                                                                                                                                                                                                                                    Apr 27, 2022    4 - Beta               pytest (>=7.1.1,<8.0.0)
+   :pypi:`pytest-pretty`                            pytest plugin for printing summary data as I want it                                                                                                                                                                                                                                                                                                                                    Apr 05, 2023    5 - Production/Stable  pytest>=7
+   :pypi:`pytest-pretty-terminal`                   pytest plugin for generating prettier terminal output                                                                                                                                                                                                                                                                                                                                   Jan 31, 2022    N/A                    pytest (>=3.4.1)
+   :pypi:`pytest-pride`                             Minitest-style test colors                                                                                                                                                                                                                                                                                                                                                              Apr 02, 2016    3 - Alpha              N/A
+   :pypi:`pytest-print`                             pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout)                                                                                                                                                                                                                                                             Feb 25, 2025    5 - Production/Stable  pytest>=8.3.2
+   :pypi:`pytest-priority`                          pytest plugin for add priority for tests                                                                                                                                                                                                                                                                                                                                                Aug 19, 2024    N/A                    pytest
+   :pypi:`pytest-proceed`                                                                                                                                                                                                                                                                                                                                                                                                                   Oct 01, 2024    N/A                    pytest
+   :pypi:`pytest-profiles`                          pytest plugin for configuration profiles                                                                                                                                                                                                                                                                                                                                                Dec 09, 2021    4 - Beta               pytest (>=3.7.0)
+   :pypi:`pytest-profiling`                         Profiling plugin for py.test                                                                                                                                                                                                                                                                                                                                                            Nov 29, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-progress`                          pytest plugin for instant test progress status                                                                                                                                                                                                                                                                                                                                          Jun 18, 2024    5 - Production/Stable  pytest>=2.7
+   :pypi:`pytest-prometheus`                        Report test pass / failures to a Prometheus PushGateway                                                                                                                                                                                                                                                                                                                                 Oct 03, 2017    N/A                    N/A
+   :pypi:`pytest-prometheus-pushgateway`            Pytest report plugin for Zulip                                                                                                                                                                                                                                                                                                                                                          Sep 27, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest-prosper`                           Test helpers for Prosper projects                                                                                                                                                                                                                                                                                                                                                       Sep 24, 2018    N/A                    N/A
+   :pypi:`pytest-prysk`                             Pytest plugin for prysk                                                                                                                                                                                                                                                                                                                                                                 Dec 10, 2024    4 - Beta               pytest>=7.3.2
+   :pypi:`pytest-pspec`                             A rspec format reporter for Python ptest                                                                                                                                                                                                                                                                                                                                                Jun 02, 2020    4 - Beta               pytest (>=3.0.0)
+   :pypi:`pytest-psqlgraph`                         pytest plugin for testing applications that use psqlgraph                                                                                                                                                                                                                                                                                                                               Oct 19, 2021    4 - Beta               pytest (>=6.0)
+   :pypi:`pytest-pt`                                pytest plugin to use \*.pt files as tests                                                                                                                                                                                                                                                                                                                                               Sep 22, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-ptera`                             Use ptera probes in tests                                                                                                                                                                                                                                                                                                                                                               Mar 01, 2022    N/A                    pytest (>=6.2.4,<7.0.0)
+   :pypi:`pytest-publish`                                                                                                                                                                                                                                                                                                                                                                                                                   Jun 04, 2024    N/A                    pytest<9.0.0,>=8.0.0
+   :pypi:`pytest-pudb`                              Pytest PuDB debugger integration                                                                                                                                                                                                                                                                                                                                                        Oct 25, 2018    3 - Alpha              pytest (>=2.0)
+   :pypi:`pytest-pumpkin-spice`                     A pytest plugin that makes your test reporting pumpkin-spiced                                                                                                                                                                                                                                                                                                                           Sep 18, 2022    4 - Beta               N/A
+   :pypi:`pytest-purkinje`                          py.test plugin for purkinje test runner                                                                                                                                                                                                                                                                                                                                                 Oct 28, 2017    2 - Pre-Alpha          N/A
+   :pypi:`pytest-pusher`                            pytest plugin for push report to minio                                                                                                                                                                                                                                                                                                                                                  Jan 06, 2023    5 - Production/Stable  pytest (>=3.6)
+   :pypi:`pytest-py125`                                                                                                                                                                                                                                                                                                                                                                                                                     Dec 03, 2022    N/A                    N/A
+   :pypi:`pytest-pycharm`                           Plugin for py.test to enter PyCharm debugger on uncaught exceptions                                                                                                                                                                                                                                                                                                                     Aug 13, 2020    5 - Production/Stable  pytest (>=2.3)
+   :pypi:`pytest-pycodestyle`                       pytest plugin to run pycodestyle                                                                                                                                                                                                                                                                                                                                                        Oct 10, 2024    3 - Alpha              pytest>=7.0
+   :pypi:`pytest-pydantic-schema-sync`              Pytest plugin to synchronise Pydantic model schemas with JSONSchema files                                                                                                                                                                                                                                                                                                               Aug 29, 2024    N/A                    pytest>=6
+   :pypi:`pytest-pydev`                             py.test plugin to connect to a remote debug server with PyDev or PyCharm.                                                                                                                                                                                                                                                                                                               Nov 15, 2017    3 - Alpha              N/A
+   :pypi:`pytest-pydocstyle`                        pytest plugin to run pydocstyle                                                                                                                                                                                                                                                                                                                                                         Oct 09, 2024    3 - Alpha              pytest>=7.0
+   :pypi:`pytest-pylint`                            pytest plugin to check source code with pylint                                                                                                                                                                                                                                                                                                                                          Oct 06, 2023    5 - Production/Stable  pytest >=7.0
+   :pypi:`pytest-pylyzer`                           A pytest plugin for pylyzer                                                                                                                                                                                                                                                                                                                                                             Feb 15, 2025    4 - Beta               N/A
+   :pypi:`pytest-pymysql-autorecord`                Record PyMySQL queries and mock with the stored data.                                                                                                                                                                                                                                                                                                                                   Sep 02, 2022    N/A                    N/A
+   :pypi:`pytest-pyodide`                           Pytest plugin for testing applications that use Pyodide                                                                                                                                                                                                                                                                                                                                 Nov 23, 2024    N/A                    pytest
+   :pypi:`pytest-pypi`                              Easily test your HTTP library against a local copy of pypi                                                                                                                                                                                                                                                                                                                              Mar 04, 2018    3 - Alpha              N/A
+   :pypi:`pytest-pypom-navigation`                  Core engine for cookiecutter-qa and pytest-play packages                                                                                                                                                                                                                                                                                                                                Feb 18, 2019    4 - Beta               pytest (>=3.0.7)
+   :pypi:`pytest-pyppeteer`                         A plugin to run pyppeteer in pytest                                                                                                                                                                                                                                                                                                                                                     Apr 28, 2022    N/A                    pytest (>=6.2.5,<7.0.0)
+   :pypi:`pytest-pyq`                               Pytest fixture "q" for pyq                                                                                                                                                                                                                                                                                                                                                              Mar 10, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-pyramid`                           pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite                                                                                                                                                                                                                                                                                              Oct 24, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-pyramid-server`                    Pyramid server fixture for py.test                                                                                                                                                                                                                                                                                                                                                      Oct 17, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-pyreport`                          PyReport is a lightweight reporting plugin for Pytest that provides concise HTML report                                                                                                                                                                                                                                                                                                 May 05, 2024    N/A                    pytest
+   :pypi:`pytest-pyright`                           Pytest plugin for type checking code with Pyright                                                                                                                                                                                                                                                                                                                                       Jan 26, 2024    4 - Beta               pytest >=7.0.0
+   :pypi:`pytest-pyspec`                            A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it".                                                                                                                                                                                Aug 17, 2024    N/A                    pytest<9.0.0,>=8.3.2
+   :pypi:`pytest-pystack`                           Plugin to run pystack after a timeout for a test suite.                                                                                                                                                                                                                                                                                                                                 Nov 16, 2024    N/A                    pytest>=3.5.0
+   :pypi:`pytest-pytestrail`                        Pytest plugin for interaction with TestRail                                                                                                                                                                                                                                                                                                                                             Aug 27, 2020    4 - Beta               pytest (>=3.8.0)
+   :pypi:`pytest-pythonhashseed`                    Pytest plugin to set PYTHONHASHSEED env var.                                                                                                                                                                                                                                                                                                                                            Feb 25, 2024    4 - Beta               pytest>=3.0.0
+   :pypi:`pytest-pythonpath`                        pytest plugin for adding to the PYTHONPATH from command line or configs.                                                                                                                                                                                                                                                                                                                Feb 10, 2022    5 - Production/Stable  pytest (<7,>=2.5.2)
+   :pypi:`pytest-python-test-engineer-sort`         Sort plugin for Pytest                                                                                                                                                                                                                                                                                                                                                                  May 13, 2024    N/A                    pytest>=6.2.0
+   :pypi:`pytest-pytorch`                           pytest plugin for a better developer experience when working with the PyTorch test suite                                                                                                                                                                                                                                                                                                May 25, 2021    4 - Beta               pytest
+   :pypi:`pytest-pyvenv`                            A package for create venv in tests                                                                                                                                                                                                                                                                                                                                                      Feb 27, 2024    N/A                    pytest ; extra == 'test'
+   :pypi:`pytest-pyvista`                           Pytest-pyvista package                                                                                                                                                                                                                                                                                                                                                                  Sep 29, 2023    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-qanova`                            A pytest plugin to collect test information                                                                                                                                                                                                                                                                                                                                             Sep 05, 2024    3 - Alpha              pytest
+   :pypi:`pytest-qaseio`                            Pytest plugin for Qase.io integration                                                                                                                                                                                                                                                                                                                                                   Mar 18, 2025    5 - Production/Stable  pytest<9.0.0,>=7.2.2
+   :pypi:`pytest-qasync`                            Pytest support for qasync.                                                                                                                                                                                                                                                                                                                                                              Jul 12, 2021    4 - Beta               pytest (>=5.4.0)
+   :pypi:`pytest-qatouch`                           Pytest plugin for uploading test results to your QA Touch Testrun.                                                                                                                                                                                                                                                                                                                      Feb 14, 2023    4 - Beta               pytest (>=6.2.0)
+   :pypi:`pytest-qgis`                              A pytest plugin for testing QGIS python plugins                                                                                                                                                                                                                                                                                                                                         Jun 14, 2024    5 - Production/Stable  pytest>=6.0
+   :pypi:`pytest-qml`                               Run QML Tests with pytest                                                                                                                                                                                                                                                                                                                                                               Dec 02, 2020    4 - Beta               pytest (>=6.0.0)
+   :pypi:`pytest-qr`                                pytest plugin to generate test result QR codes                                                                                                                                                                                                                                                                                                                                          Nov 25, 2021    4 - Beta               N/A
+   :pypi:`pytest-qt`                                pytest support for PyQt and PySide applications                                                                                                                                                                                                                                                                                                                                         Feb 07, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-qt-app`                            QT app fixture for py.test                                                                                                                                                                                                                                                                                                                                                              Oct 17, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-quarantine`                        A plugin for pytest to manage expected test failures                                                                                                                                                                                                                                                                                                                                    Nov 24, 2019    5 - Production/Stable  pytest (>=4.6)
+   :pypi:`pytest-quickcheck`                        pytest plugin to generate random data inspired by QuickCheck                                                                                                                                                                                                                                                                                                                            Nov 05, 2022    4 - Beta               pytest (>=4.0)
+   :pypi:`pytest_quickify`                          Run test suites with pytest-quickify.                                                                                                                                                                                                                                                                                                                                                   Jun 14, 2019    N/A                    pytest
+   :pypi:`pytest-rabbitmq`                          RabbitMQ process and client fixtures for pytest                                                                                                                                                                                                                                                                                                                                         Oct 15, 2024    5 - Production/Stable  pytest>=6.2
+   :pypi:`pytest-race`                              Race conditions tester for pytest                                                                                                                                                                                                                                                                                                                                                       Jun 07, 2022    4 - Beta               N/A
+   :pypi:`pytest-rage`                              pytest plugin to implement PEP712                                                                                                                                                                                                                                                                                                                                                       Oct 21, 2011    3 - Alpha              N/A
+   :pypi:`pytest-rail`                              pytest plugin for creating TestRail runs and adding results                                                                                                                                                                                                                                                                                                                             May 02, 2022    N/A                    pytest (>=3.6)
+   :pypi:`pytest-railflow-testrail-reporter`        Generate json reports along with specified metadata defined in test markers.                                                                                                                                                                                                                                                                                                            Jun 29, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest-raises`                            An implementation of pytest.raises as a pytest.mark fixture                                                                                                                                                                                                                                                                                                                             Apr 23, 2020    N/A                    pytest (>=3.2.2)
+   :pypi:`pytest-raisesregexp`                      Simple pytest plugin to look for regex in Exceptions                                                                                                                                                                                                                                                                                                                                    Dec 18, 2015    N/A                    N/A
+   :pypi:`pytest-raisin`                            Plugin enabling the use of exception instances with pytest.raises                                                                                                                                                                                                                                                                                                                       Feb 06, 2022    N/A                    pytest
+   :pypi:`pytest-random`                            py.test plugin to randomize tests                                                                                                                                                                                                                                                                                                                                                       Apr 28, 2013    3 - Alpha              N/A
+   :pypi:`pytest-randomly`                          Pytest plugin to randomly order tests and control random.seed.                                                                                                                                                                                                                                                                                                                          Oct 25, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-randomness`                        Pytest plugin about random seed management                                                                                                                                                                                                                                                                                                                                              May 30, 2019    3 - Alpha              N/A
+   :pypi:`pytest-random-num`                        Randomise the order in which pytest tests are run with some control over the randomness                                                                                                                                                                                                                                                                                                 Oct 19, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-random-order`                      Randomise the order in which pytest tests are run with some control over the randomness                                                                                                                                                                                                                                                                                                 Jan 20, 2024    5 - Production/Stable  pytest >=3.0.0
+   :pypi:`pytest-ranking`                           A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection                                                                                                                                                                                                                                                                                              Jan 14, 2025    4 - Beta               pytest>=7.4.3
+   :pypi:`pytest-readme`                            Test your README.md file                                                                                                                                                                                                                                                                                                                                                                Sep 02, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-reana`                             Pytest fixtures for REANA.                                                                                                                                                                                                                                                                                                                                                              Sep 04, 2024    3 - Alpha              N/A
+   :pypi:`pytest-recorder`                          Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs.                                                                                                                                                                                                                                                                                                    Mar 31, 2025    N/A                    N/A
+   :pypi:`pytest-recording`                         A pytest plugin that allows you recording of network interactions via VCR.py                                                                                                                                                                                                                                                                                                            Jul 09, 2024    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-recordings`                        Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal                                                                                                                                                                                                                                                                                   Aug 13, 2020    N/A                    N/A
+   :pypi:`pytest-record-video`                      用例执行过程中录制视频                                                                                                                                                                                                                                                                                                                                                                  Oct 31, 2024    N/A                    N/A
+   :pypi:`pytest-redis`                             Redis fixtures and fixture factories for Pytest.                                                                                                                                                                                                                                                                                                                                        Nov 27, 2024    5 - Production/Stable  pytest>=6.2
+   :pypi:`pytest-redislite`                         Pytest plugin for testing code using Redis                                                                                                                                                                                                                                                                                                                                              Apr 05, 2022    4 - Beta               pytest
+   :pypi:`pytest-redmine`                           Pytest plugin for redmine                                                                                                                                                                                                                                                                                                                                                               Mar 19, 2018    1 - Planning           N/A
+   :pypi:`pytest-ref`                               A plugin to store reference files to ease regression testing                                                                                                                                                                                                                                                                                                                            Nov 23, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-reference-formatter`               Conveniently run pytest with a dot-formatted test reference.                                                                                                                                                                                                                                                                                                                            Oct 01, 2019    4 - Beta               N/A
+   :pypi:`pytest-regex`                             Select pytest tests with regular expressions                                                                                                                                                                                                                                                                                                                                            May 29, 2023    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-regex-dependency`                  Management of Pytest dependencies via regex patterns                                                                                                                                                                                                                                                                                                                                    Jun 12, 2022    N/A                    pytest
+   :pypi:`pytest-regressions`                       Easy to use fixtures to write regression tests.                                                                                                                                                                                                                                                                                                                                         Jan 10, 2025    5 - Production/Stable  pytest>=6.2.0
+   :pypi:`pytest-regtest`                           pytest plugin for snapshot regression testing                                                                                                                                                                                                                                                                                                                                           Nov 12, 2024    N/A                    pytest>7.2
+   :pypi:`pytest-relative-order`                    a pytest plugin that sorts tests using "before" and "after" markers                                                                                                                                                                                                                                                                                                                     May 17, 2021    4 - Beta               N/A
+   :pypi:`pytest-relative-path`                     Handle relative path in pytest options or ini configs                                                                                                                                                                                                                                                                                                                                   Aug 30, 2024    N/A                    pytest
+   :pypi:`pytest-relaxed`                           Relaxed test discovery/organization for pytest                                                                                                                                                                                                                                                                                                                                          Mar 29, 2024    5 - Production/Stable  pytest>=7
+   :pypi:`pytest-remfiles`                          Pytest plugin to create a temporary directory with remote files                                                                                                                                                                                                                                                                                                                         Jul 01, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-remotedata`                        Pytest plugin for controlling remote data access.                                                                                                                                                                                                                                                                                                                                       Sep 26, 2023    5 - Production/Stable  pytest >=4.6
+   :pypi:`pytest-remote-response`                   Pytest plugin for capturing and mocking connection requests.                                                                                                                                                                                                                                                                                                                            Apr 26, 2023    5 - Production/Stable  pytest (>=4.6)
+   :pypi:`pytest-remove-stale-bytecode`             py.test plugin to remove stale byte code files.                                                                                                                                                                                                                                                                                                                                         Jul 07, 2023    4 - Beta               pytest
+   :pypi:`pytest-reorder`                           Reorder tests depending on their paths and names.                                                                                                                                                                                                                                                                                                                                       May 31, 2018    4 - Beta               pytest
+   :pypi:`pytest-repeat`                            pytest plugin for repeating tests                                                                                                                                                                                                                                                                                                                                                       Oct 09, 2023    5 - Production/Stable  pytest
+   :pypi:`pytest_repeater`                          py.test plugin for repeating single test multiple times.                                                                                                                                                                                                                                                                                                                                Feb 09, 2018    1 - Planning           N/A
+   :pypi:`pytest-replay`                            Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests                                                                                                                                                                                                                                                                                  Feb 05, 2025    5 - Production/Stable  pytest
+   :pypi:`pytest-repo-health`                       A pytest plugin to report on repository standards conformance                                                                                                                                                                                                                                                                                                                           Mar 12, 2025    3 - Alpha              pytest
+   :pypi:`pytest-report`                            Creates json report that is compatible with atom.io's linter message format                                                                                                                                                                                                                                                                                                             May 11, 2016    4 - Beta               N/A
+   :pypi:`pytest-reporter`                          Generate Pytest reports with templates                                                                                                                                                                                                                                                                                                                                                  Feb 28, 2024    4 - Beta               pytest
+   :pypi:`pytest-reporter-html1`                    A basic HTML report template for Pytest                                                                                                                                                                                                                                                                                                                                                 Oct 11, 2024    4 - Beta               N/A
+   :pypi:`pytest-reporter-html-dots`                A basic HTML report for pytest using Jinja2 template engine.                                                                                                                                                                                                                                                                                                                            Jan 22, 2023    N/A                    N/A
+   :pypi:`pytest-report-extras`                     Pytest plugin to enhance pytest-html and allure reports by adding comments, screenshots, webpage sources and attachments.                                                                                                                                                                                                                                                               Apr 04, 2025    N/A                    pytest>=8.0.0
+   :pypi:`pytest-reportinfra`                       Pytest plugin for reportinfra                                                                                                                                                                                                                                                                                                                                                           Aug 11, 2019    3 - Alpha              N/A
+   :pypi:`pytest-reporting`                         A plugin to report summarized results in a table format                                                                                                                                                                                                                                                                                                                                 Oct 25, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-reportlog`                         Replacement for the --resultlog option, focused in simplicity and extensibility                                                                                                                                                                                                                                                                                                         May 22, 2023    3 - Alpha              pytest
+   :pypi:`pytest-report-me`                         A pytest plugin to generate report.                                                                                                                                                                                                                                                                                                                                                     Dec 31, 2020    N/A                    pytest
+   :pypi:`pytest-report-parameters`                 pytest plugin for adding tests' parameters to junit report                                                                                                                                                                                                                                                                                                                              Jun 18, 2020    3 - Alpha              pytest (>=2.4.2)
+   :pypi:`pytest-reportportal`                      Agent for Reporting results of tests to the Report Portal                                                                                                                                                                                                                                                                                                                               Feb 28, 2025    N/A                    pytest>=4.6.10
+   :pypi:`pytest-report-stream`                     A pytest plugin which allows to stream test reports at runtime                                                                                                                                                                                                                                                                                                                          Oct 22, 2023    4 - Beta               N/A
+   :pypi:`pytest-repo-structure`                    Pytest Repo Structure                                                                                                                                                                                                                                                                                                                                                                   Mar 18, 2024    1 - Planning           N/A
+   :pypi:`pytest-req`                               pytest requests plugin                                                                                                                                                                                                                                                                                                                                                                  Aug 31, 2024    5 - Production/Stable  pytest<9.0.0,>=8.3.2
+   :pypi:`pytest-reqs`                              pytest plugin to check pinned requirements                                                                                                                                                                                                                                                                                                                                              May 12, 2019    N/A                    pytest (>=2.4.2)
+   :pypi:`pytest-requests`                          A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Jun 24, 2019    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-requestselapsed`                   collect and show http requests elapsed time                                                                                                                                                                                                                                                                                                                                             Aug 14, 2022    N/A                    N/A
+   :pypi:`pytest-requests-futures`                  Pytest Plugin to Mock Requests Futures                                                                                                                                                                                                                                                                                                                                                  Jul 06, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest-requirements`                      pytest plugin for using custom markers to relate tests to requirements and usecases                                                                                                                                                                                                                                                                                                     Feb 28, 2025    N/A                    pytest
+   :pypi:`pytest-requires`                          A pytest plugin to elegantly skip tests with optional requirements                                                                                                                                                                                                                                                                                                                      Dec 21, 2021    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-reraise`                           Make multi-threaded pytest test cases fail when they should                                                                                                                                                                                                                                                                                                                             Sep 20, 2022    5 - Production/Stable  pytest (>=4.6)
+   :pypi:`pytest-rerun`                             Re-run only changed files in specified branch                                                                                                                                                                                                                                                                                                                                           Jul 08, 2019    N/A                    pytest (>=3.6)
+   :pypi:`pytest-rerun-all`                         Rerun testsuite for a certain time or iterations                                                                                                                                                                                                                                                                                                                                        Nov 16, 2023    3 - Alpha              pytest (>=7.0.0)
+   :pypi:`pytest-rerunclassfailures`                pytest rerun class failures plugin                                                                                                                                                                                                                                                                                                                                                      Apr 24, 2024    5 - Production/Stable  pytest>=7.2
+   :pypi:`pytest-rerunfailures`                     pytest plugin to re-run tests to eliminate flaky failures                                                                                                                                                                                                                                                                                                                               Nov 20, 2024    5 - Production/Stable  pytest!=8.2.2,>=7.4
+   :pypi:`pytest-rerunfailures-all-logs`            pytest plugin to re-run tests to eliminate flaky failures                                                                                                                                                                                                                                                                                                                               Mar 07, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-reserial`                          Pytest fixture for recording and replaying serial port traffic.                                                                                                                                                                                                                                                                                                                         Dec 22, 2024    4 - Beta               pytest
+   :pypi:`pytest-resilient-circuits`                Resilient Circuits fixtures for PyTest                                                                                                                                                                                                                                                                                                                                                  Feb 28, 2025    N/A                    pytest~=7.0
+   :pypi:`pytest-resource`                          Load resource fixture plugin to use with pytest                                                                                                                                                                                                                                                                                                                                         Nov 14, 2018    4 - Beta               N/A
+   :pypi:`pytest-resource-path`                     Provides path for uniform access to test resources in isolated directory                                                                                                                                                                                                                                                                                                                May 01, 2021    5 - Production/Stable  pytest (>=3.5.0)
+   :pypi:`pytest-resource-usage`                    Pytest plugin for reporting running time and peak memory usage                                                                                                                                                                                                                                                                                                                          Nov 06, 2022    5 - Production/Stable  pytest>=7.0.0
+   :pypi:`pytest-responsemock`                      Simplified requests calls mocking for pytest                                                                                                                                                                                                                                                                                                                                            Mar 10, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-responses`                         py.test integration for responses                                                                                                                                                                                                                                                                                                                                                       Oct 11, 2022    N/A                    pytest (>=2.5)
+   :pypi:`pytest-rest-api`                                                                                                                                                                                                                                                                                                                                                                                                                  Aug 08, 2022    N/A                    pytest (>=7.1.2,<8.0.0)
+   :pypi:`pytest-restrict`                          Pytest plugin to restrict the test types allowed                                                                                                                                                                                                                                                                                                                                        Oct 24, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-result-log`                        A pytest plugin that records the start, end, and result information of each use case in a log file                                                                                                                                                                                                                                                                                      Jan 10, 2024    N/A                    pytest>=7.2.0
+   :pypi:`pytest-results`                           Easily spot regressions in your tests.                                                                                                                                                                                                                                                                                                                                                  Mar 14, 2025    4 - Beta               pytest
+   :pypi:`pytest-result-sender`                                                                                                                                                                                                                                                                                                                                                                                                             Apr 20, 2023    N/A                    pytest>=7.3.1
+   :pypi:`pytest-result-sender-lj`                  Default template for PDM package                                                                                                                                                                                                                                                                                                                                                        Dec 17, 2024    N/A                    pytest>=8.3.4
+   :pypi:`pytest-result-sender-lyt`                 Default template for PDM package                                                                                                                                                                                                                                                                                                                                                        Mar 14, 2025    N/A                    pytest>=8.3.5
+   :pypi:`pytest-result-sender-misszhang`           Default template for PDM package                                                                                                                                                                                                                                                                                                                                                        Mar 21, 2025    N/A                    pytest>=8.3.5
+   :pypi:`pytest-resume`                            A Pytest plugin to resuming from the last run test                                                                                                                                                                                                                                                                                                                                      Apr 22, 2023    4 - Beta               pytest (>=7.0)
+   :pypi:`pytest-rethinkdb`                         A RethinkDB plugin for pytest.                                                                                                                                                                                                                                                                                                                                                          Jul 24, 2016    4 - Beta               N/A
+   :pypi:`pytest-retry`                             Adds the ability to retry flaky tests in CI environments                                                                                                                                                                                                                                                                                                                                Jan 19, 2025    N/A                    pytest>=7.0.0
+   :pypi:`pytest-retry-class`                       A pytest plugin to rerun entire class on failure                                                                                                                                                                                                                                                                                                                                        Nov 24, 2024    N/A                    pytest>=5.3
+   :pypi:`pytest-reusable-testcases`                                                                                                                                                                                                                                                                                                                                                                                                        Apr 28, 2023    N/A                    N/A
+   :pypi:`pytest-revealtype-injector`               Pytest plugin for replacing reveal_type() calls inside test functions with static and runtime type checking result comparison, for confirming type annotation validity.                                                                                                                                                                                                                 Mar 18, 2025    4 - Beta               pytest<9,>=7.0
+   :pypi:`pytest-reverse`                           Pytest plugin to reverse test order.                                                                                                                                                                                                                                                                                                                                                    Oct 25, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-rich`                              Leverage rich for richer test session output                                                                                                                                                                                                                                                                                                                                            Dec 12, 2024    4 - Beta               pytest>=7.0
+   :pypi:`pytest-richer`                            Pytest plugin providing a Rich based reporter.                                                                                                                                                                                                                                                                                                                                          Oct 27, 2023    3 - Alpha              pytest
+   :pypi:`pytest-rich-reporter`                     A pytest plugin using Rich for beautiful test result formatting.                                                                                                                                                                                                                                                                                                                        Feb 17, 2022    1 - Planning           pytest (>=5.0.0)
+   :pypi:`pytest-richtrace`                         A pytest plugin that displays the names and information of the pytest hook functions as they are executed.                                                                                                                                                                                                                                                                              Jun 20, 2023    N/A                    N/A
+   :pypi:`pytest-ringo`                             pytest plugin to test webapplications using the Ringo webframework                                                                                                                                                                                                                                                                                                                      Sep 27, 2017    3 - Alpha              N/A
+   :pypi:`pytest-rmsis`                             Sycronise pytest results to Jira RMsis                                                                                                                                                                                                                                                                                                                                                  Aug 10, 2022    N/A                    pytest (>=5.3.5)
+   :pypi:`pytest-rng`                               Fixtures for seeding tests and making randomness reproducible                                                                                                                                                                                                                                                                                                                           Aug 08, 2019    5 - Production/Stable  pytest
+   :pypi:`pytest-roast`                             pytest plugin for ROAST configuration override and fixtures                                                                                                                                                                                                                                                                                                                             Nov 09, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest_robotframework`                    a pytest plugin that can run both python and robotframework tests while generating robot reports for them                                                                                                                                                                                                                                                                               Mar 28, 2025    N/A                    pytest<9,>=7
+   :pypi:`pytest-rocketchat`                        Pytest to Rocket.Chat reporting plugin                                                                                                                                                                                                                                                                                                                                                  Apr 18, 2021    5 - Production/Stable  N/A
+   :pypi:`pytest-rotest`                            Pytest integration with rotest                                                                                                                                                                                                                                                                                                                                                          Sep 08, 2019    N/A                    pytest (>=3.5.0)
+   :pypi:`pytest-rpc`                               Extend py.test for RPC OpenStack testing.                                                                                                                                                                                                                                                                                                                                               Feb 22, 2019    4 - Beta               pytest (~=3.6)
+   :pypi:`pytest-rst`                               Test code from RST documents with pytest                                                                                                                                                                                                                                                                                                                                                Jan 26, 2023    N/A                    N/A
+   :pypi:`pytest-rt`                                pytest data collector plugin for Testgr                                                                                                                                                                                                                                                                                                                                                 May 05, 2022    N/A                    N/A
+   :pypi:`pytest-rts`                               Coverage-based regression test selection (RTS) plugin for pytest                                                                                                                                                                                                                                                                                                                        May 17, 2021    N/A                    pytest
+   :pypi:`pytest-ruff`                              pytest plugin to check ruff requirements.                                                                                                                                                                                                                                                                                                                                               Jul 21, 2024    4 - Beta               pytest>=5
+   :pypi:`pytest-run-changed`                       Pytest plugin that runs changed tests only                                                                                                                                                                                                                                                                                                                                              Apr 02, 2021    3 - Alpha              pytest
+   :pypi:`pytest-runfailed`                         implement a --failed option for pytest                                                                                                                                                                                                                                                                                                                                                  Mar 24, 2016    N/A                    N/A
+   :pypi:`pytest-run-parallel`                      A simple pytest plugin to run tests concurrently                                                                                                                                                                                                                                                                                                                                        Feb 05, 2025    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-run-subprocess`                    Pytest Plugin for running and testing subprocesses.                                                                                                                                                                                                                                                                                                                                     Nov 12, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest-runtime-types`                     Checks type annotations on runtime while running tests.                                                                                                                                                                                                                                                                                                                                 Feb 09, 2023    N/A                    pytest
+   :pypi:`pytest-runtime-xfail`                     Call runtime_xfail() to mark running test as xfail.                                                                                                                                                                                                                                                                                                                                     Aug 26, 2021    N/A                    pytest>=5.0.0
+   :pypi:`pytest-runtime-yoyo`                      run case mark timeout                                                                                                                                                                                                                                                                                                                                                                   Jun 12, 2023    N/A                    pytest (>=7.2.0)
+   :pypi:`pytest-saccharin`                         pytest-saccharin is a updated fork of pytest-sugar, a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly).                                                                                                                                                                                                            Oct 31, 2022    3 - Alpha              N/A
+   :pypi:`pytest-salt`                              Pytest Salt Plugin                                                                                                                                                                                                                                                                                                                                                                      Jan 27, 2020    4 - Beta               N/A
+   :pypi:`pytest-salt-containers`                   A Pytest plugin that builds and creates docker containers                                                                                                                                                                                                                                                                                                                               Nov 09, 2016    4 - Beta               N/A
+   :pypi:`pytest-salt-factories`                    Pytest Salt Plugin                                                                                                                                                                                                                                                                                                                                                                      Oct 22, 2024    5 - Production/Stable  pytest>=7.4.0
+   :pypi:`pytest-salt-from-filenames`               Simple PyTest Plugin For Salt's Test Suite Specifically                                                                                                                                                                                                                                                                                                                                 Jan 29, 2019    4 - Beta               pytest (>=4.1)
+   :pypi:`pytest-salt-runtests-bridge`              Simple PyTest Plugin For Salt's Test Suite Specifically                                                                                                                                                                                                                                                                                                                                 Dec 05, 2019    4 - Beta               pytest (>=4.1)
+   :pypi:`pytest-sample-argvalues`                  A utility function to help choose a random sample from your argvalues in pytest.                                                                                                                                                                                                                                                                                                        May 07, 2024    N/A                    pytest
+   :pypi:`pytest-sanic`                             a pytest plugin for Sanic                                                                                                                                                                                                                                                                                                                                                               Oct 25, 2021    N/A                    pytest (>=5.2)
+   :pypi:`pytest-sanitizer`                         A pytest plugin to sanitize output for LLMs (personal tool, no warranty or liability)                                                                                                                                                                                                                                                                                                   Mar 16, 2025    3 - Alpha              pytest>=6.0.0
+   :pypi:`pytest-sanity`                                                                                                                                                                                                                                                                                                                                                                                                                    Dec 07, 2020    N/A                    N/A
+   :pypi:`pytest-sa-pg`                                                                                                                                                                                                                                                                                                                                                                                                                     May 14, 2019    N/A                    N/A
+   :pypi:`pytest_sauce`                             pytest_sauce provides sane and helpful methods worked    out in clearcode to run py.test tests with selenium/saucelabs                                                                                                                                                                                                                                                                  Jul 14, 2014    3 - Alpha              N/A
+   :pypi:`pytest-sbase`                             A complete web automation framework for end-to-end testing.                                                                                                                                                                                                                                                                                                                             Apr 04, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-scenario`                          pytest plugin for test scenarios                                                                                                                                                                                                                                                                                                                                                        Feb 06, 2017    3 - Alpha              N/A
+   :pypi:`pytest-scenario-files`                    A pytest plugin that generates unit test scenarios from data files.                                                                                                                                                                                                                                                                                                                     Nov 21, 2024    5 - Production/Stable  pytest>=7.0
+   :pypi:`pytest-schedule`                          Automate and customize test scheduling effortlessly on local machines.                                                                                                                                                                                                                                                                                                                  Oct 31, 2024    N/A                    N/A
+   :pypi:`pytest-schema`                            👍 Validate return values against a schema-like object in testing                                                                                                                                                                                                                                                                                                                       Feb 16, 2024    5 - Production/Stable  pytest >=3.5.0
+   :pypi:`pytest-scim2-server`                      SCIM2 server fixture for Pytest                                                                                                                                                                                                                                                                                                                                                         Mar 28, 2025    N/A                    pytest>=8.3.4
+   :pypi:`pytest-screenshot-on-failure`             Saves a screenshot when a test case from a pytest execution fails                                                                                                                                                                                                                                                                                                                       Jul 21, 2023    4 - Beta               N/A
+   :pypi:`pytest-scrutinize`                        Scrutinize your pytest test suites for slow fixtures, tests and more.                                                                                                                                                                                                                                                                                                                   Aug 19, 2024    4 - Beta               pytest>=6
+   :pypi:`pytest-securestore`                       An encrypted password store for use within pytest cases                                                                                                                                                                                                                                                                                                                                 Nov 08, 2021    4 - Beta               N/A
+   :pypi:`pytest-select`                            A pytest plugin which allows to (de-)select tests from a file.                                                                                                                                                                                                                                                                                                                          Jan 18, 2019    3 - Alpha              pytest (>=3.0)
+   :pypi:`pytest-selenium`                          pytest plugin for Selenium                                                                                                                                                                                                                                                                                                                                                              Feb 01, 2024    5 - Production/Stable  pytest>=6.0.0
+   :pypi:`pytest-selenium-auto`                     pytest plugin to automatically capture screenshots upon selenium webdriver events                                                                                                                                                                                                                                                                                                       Nov 07, 2023    N/A                    pytest >= 7.0.0
+   :pypi:`pytest-seleniumbase`                      A complete web automation framework for end-to-end testing.                                                                                                                                                                                                                                                                                                                             Apr 04, 2025    5 - Production/Stable  N/A
+   :pypi:`pytest-selenium-enhancer`                 pytest plugin for Selenium                                                                                                                                                                                                                                                                                                                                                              Apr 29, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-selenium-pdiff`                    A pytest package implementing perceptualdiff for Selenium tests.                                                                                                                                                                                                                                                                                                                        Apr 06, 2017    2 - Pre-Alpha          N/A
+   :pypi:`pytest-selfie`                            A pytest plugin for selfie snapshot testing.                                                                                                                                                                                                                                                                                                                                            Dec 16, 2024    N/A                    pytest>=8.0.0
+   :pypi:`pytest-send-email`                        Send pytest execution result email                                                                                                                                                                                                                                                                                                                                                      Sep 02, 2024    N/A                    pytest
+   :pypi:`pytest-sentry`                            A pytest plugin to send testrun information to Sentry.io                                                                                                                                                                                                                                                                                                                                Feb 24, 2025    N/A                    pytest
+   :pypi:`pytest-sequence-markers`                  Pytest plugin for sequencing markers for execution of tests                                                                                                                                                                                                                                                                                                                             May 23, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-server`                            test server exec cmd                                                                                                                                                                                                                                                                                                                                                                    Sep 09, 2024    N/A                    N/A
+   :pypi:`pytest-server-fixtures`                   Extensible server fixtures for py.test                                                                                                                                                                                                                                                                                                                                                  Nov 29, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-serverless`                        Automatically mocks resources from serverless.yml in pytest using moto.                                                                                                                                                                                                                                                                                                                 May 09, 2022    4 - Beta               N/A
+   :pypi:`pytest-servers`                           pytest servers                                                                                                                                                                                                                                                                                                                                                                          Mar 12, 2025    3 - Alpha              pytest>=6.2
+   :pypi:`pytest-service`                                                                                                                                                                                                                                                                                                                                                                                                                   Aug 06, 2024    5 - Production/Stable  pytest>=6.0.0
+   :pypi:`pytest-services`                          Services plugin for pytest testing framework                                                                                                                                                                                                                                                                                                                                            Oct 30, 2020    6 - Mature             N/A
+   :pypi:`pytest-session2file`                      pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test.                                                                                                                                                                                                                                       Jan 26, 2021    3 - Alpha              pytest
+   :pypi:`pytest-session-fixture-globalize`         py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules                                                                                                                                                                                                                                                                         May 15, 2018    4 - Beta               N/A
+   :pypi:`pytest-session_to_file`                   pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test.                                                                                                                                                                                                                                                                                      Oct 01, 2015    3 - Alpha              N/A
+   :pypi:`pytest-setupinfo`                         Displaying setup info during pytest command run                                                                                                                                                                                                                                                                                                                                         Jan 23, 2023    N/A                    N/A
+   :pypi:`pytest-sftpserver`                        py.test plugin to locally test sftp server connections.                                                                                                                                                                                                                                                                                                                                 Sep 16, 2019    4 - Beta               N/A
+   :pypi:`pytest-shard`                                                                                                                                                                                                                                                                                                                                                                                                                     Dec 11, 2020    4 - Beta               pytest
+   :pypi:`pytest-shared-session-scope`              Pytest session-scoped fixture that works with xdist                                                                                                                                                                                                                                                                                                                                     Sep 22, 2024    N/A                    pytest>=7.0.0
+   :pypi:`pytest-share-hdf`                         Plugin to save test data in HDF files and retrieve them for comparison                                                                                                                                                                                                                                                                                                                  Sep 21, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-sharkreport`                       this is pytest report plugin.                                                                                                                                                                                                                                                                                                                                                           Jul 11, 2022    N/A                    pytest (>=3.5)
+   :pypi:`pytest-shell`                             A pytest plugin to help with testing shell scripts / black box commands                                                                                                                                                                                                                                                                                                                 Mar 27, 2022    N/A                    N/A
+   :pypi:`pytest-shell-utilities`                   Pytest plugin to simplify running shell commands against the system                                                                                                                                                                                                                                                                                                                     Oct 22, 2024    5 - Production/Stable  pytest>=7.4.0
+   :pypi:`pytest-sheraf`                            Versatile ZODB abstraction layer - pytest fixtures                                                                                                                                                                                                                                                                                                                                      Feb 11, 2020    N/A                    pytest
+   :pypi:`pytest-sherlock`                          pytest plugin help to find coupled tests                                                                                                                                                                                                                                                                                                                                                Aug 14, 2023    5 - Production/Stable  pytest >=3.5.1
+   :pypi:`pytest-shortcuts`                         Expand command-line shortcuts listed in pytest configuration                                                                                                                                                                                                                                                                                                                            Oct 29, 2020    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-shutil`                            A goodie-bag of unix shell and environment tools for py.test                                                                                                                                                                                                                                                                                                                            Nov 29, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-simbind`                           Pytest plugin to operate with objects generated by Simbind tool.                                                                                                                                                                                                                                                                                                                        Mar 28, 2024    N/A                    pytest>=7.0.0
+   :pypi:`pytest-simplehttpserver`                  Simple pytest fixture to spin up an HTTP server                                                                                                                                                                                                                                                                                                                                         Jun 24, 2021    4 - Beta               N/A
+   :pypi:`pytest-simple-plugin`                     Simple pytest plugin                                                                                                                                                                                                                                                                                                                                                                    Nov 27, 2019    N/A                    N/A
+   :pypi:`pytest-simple-settings`                   simple-settings plugin for pytest                                                                                                                                                                                                                                                                                                                                                       Nov 17, 2020    4 - Beta               pytest
+   :pypi:`pytest-single-file-logging`               Allow for multiple processes to log to a single file                                                                                                                                                                                                                                                                                                                                    May 05, 2016    4 - Beta               pytest (>=2.8.1)
+   :pypi:`pytest-skip`                              A pytest plugin which allows to (de-)select or skip tests from a file.                                                                                                                                                                                                                                                                                                                  Apr 04, 2025    3 - Alpha              pytest
+   :pypi:`pytest-skip-markers`                      Pytest Salt Plugin                                                                                                                                                                                                                                                                                                                                                                      Aug 09, 2024    5 - Production/Stable  pytest>=7.1.0
+   :pypi:`pytest-skipper`                           A plugin that selects only tests with changes in execution path                                                                                                                                                                                                                                                                                                                         Mar 26, 2017    3 - Alpha              pytest (>=3.0.6)
+   :pypi:`pytest-skippy`                            Automatically skip tests that don't need to run!                                                                                                                                                                                                                                                                                                                                        Jan 27, 2018    3 - Alpha              pytest (>=2.3.4)
+   :pypi:`pytest-skip-slow`                         A pytest plugin to skip \`@pytest.mark.slow\` tests by default.                                                                                                                                                                                                                                                                                                                         Feb 09, 2023    N/A                    pytest>=6.2.0
+   :pypi:`pytest-skipuntil`                         A simple pytest plugin to skip flapping test with deadline                                                                                                                                                                                                                                                                                                                              Nov 25, 2023    4 - Beta               pytest >=3.8.0
+   :pypi:`pytest-slack`                             Pytest to Slack reporting plugin                                                                                                                                                                                                                                                                                                                                                        Dec 15, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-slow`                              A pytest plugin to skip \`@pytest.mark.slow\` tests by default.                                                                                                                                                                                                                                                                                                                         Sep 28, 2021    N/A                    N/A
+   :pypi:`pytest-slowest-first`                     Sort tests by their last duration, slowest first                                                                                                                                                                                                                                                                                                                                        Dec 11, 2022    4 - Beta               N/A
+   :pypi:`pytest-slow-first`                        Prioritize running the slowest tests first.                                                                                                                                                                                                                                                                                                                                             Jan 30, 2024    4 - Beta               pytest >=3.5.0
+   :pypi:`pytest-slow-last`                         Run tests in order of execution time (faster tests first)                                                                                                                                                                                                                                                                                                                               Mar 16, 2025    4 - Beta               pytest>=3.5.0
+   :pypi:`pytest-smartcollect`                      A plugin for collecting tests that touch changed code                                                                                                                                                                                                                                                                                                                                   Oct 04, 2018    N/A                    pytest (>=3.5.0)
+   :pypi:`pytest-smartcov`                          Smart coverage plugin for pytest.                                                                                                                                                                                                                                                                                                                                                       Sep 30, 2017    3 - Alpha              N/A
+   :pypi:`pytest-smell`                             Automated bad smell detection tool for Pytest                                                                                                                                                                                                                                                                                                                                           Jun 26, 2022    N/A                    N/A
+   :pypi:`pytest-smoke`                             Pytest plugin for smoke testing                                                                                                                                                                                                                                                                                                                                                         Mar 25, 2025    4 - Beta               pytest<9,>=7.0.0
+   :pypi:`pytest-smtp`                              Send email with pytest execution result                                                                                                                                                                                                                                                                                                                                                 Feb 20, 2021    N/A                    pytest
+   :pypi:`pytest-smtp4dev`                          Plugin for smtp4dev API                                                                                                                                                                                                                                                                                                                                                                 Jun 27, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-smtpd`                             An SMTP server for testing built on aiosmtpd                                                                                                                                                                                                                                                                                                                                            May 15, 2023    N/A                    pytest
+   :pypi:`pytest-smtp-test-server`                  pytest plugin for using \`smtp-test-server\` as a fixture                                                                                                                                                                                                                                                                                                                               Dec 03, 2023    2 - Pre-Alpha          pytest (>=7.4.3,<8.0.0)
+   :pypi:`pytest-snail`                             Plugin for adding a marker to slow running tests. 🐌                                                                                                                                                                                                                                                                                                                                    Nov 04, 2019    3 - Alpha              pytest (>=5.0.1)
+   :pypi:`pytest-snapci`                            py.test plugin for Snap-CI                                                                                                                                                                                                                                                                                                                                                              Nov 12, 2015    N/A                    N/A
+   :pypi:`pytest-snapmock`                          Snapshots for your mocks.                                                                                                                                                                                                                                                                                                                                                               Nov 15, 2024    N/A                    N/A
+   :pypi:`pytest-snapshot`                          A plugin for snapshot testing with pytest.                                                                                                                                                                                                                                                                                                                                              Apr 23, 2022    4 - Beta               pytest (>=3.0.0)
+   :pypi:`pytest-snapshot-with-message-generator`   A plugin for snapshot testing with pytest.                                                                                                                                                                                                                                                                                                                                              Jul 25, 2023    4 - Beta               pytest (>=3.0.0)
+   :pypi:`pytest-snmpserver`                                                                                                                                                                                                                                                                                                                                                                                                                May 12, 2021    N/A                    N/A
+   :pypi:`pytest-snob`                              A pytest plugin that only selects meaningful python tests to run.                                                                                                                                                                                                                                                                                                                       Jan 12, 2025    N/A                    pytest
+   :pypi:`pytest-snowflake-bdd`                     Setup test data and run tests on snowflake in BDD style!                                                                                                                                                                                                                                                                                                                                Jan 05, 2022    4 - Beta               pytest (>=6.2.0)
+   :pypi:`pytest-socket`                            Pytest Plugin to disable socket calls during tests                                                                                                                                                                                                                                                                                                                                      Jan 28, 2024    4 - Beta               pytest (>=6.2.5)
+   :pypi:`pytest-sofaepione`                        Test the installation of SOFA and the SofaEpione plugin.                                                                                                                                                                                                                                                                                                                                Aug 17, 2022    N/A                    N/A
+   :pypi:`pytest-soft-assertions`                                                                                                                                                                                                                                                                                                                                                                                                           May 05, 2020    3 - Alpha              pytest
+   :pypi:`pytest-solidity`                          A PyTest library plugin for Solidity language.                                                                                                                                                                                                                                                                                                                                          Jan 15, 2022    1 - Planning           pytest (<7,>=6.0.1) ; extra == 'tests'
+   :pypi:`pytest-solr`                              Solr process and client fixtures for py.test.                                                                                                                                                                                                                                                                                                                                           May 11, 2020    3 - Alpha              pytest (>=3.0.0)
+   :pypi:`pytest-sort`                              Tools for sorting test cases                                                                                                                                                                                                                                                                                                                                                            Mar 22, 2025    N/A                    pytest>=7.4.0
+   :pypi:`pytest-sorter`                            A simple plugin to first execute tests that historically failed more                                                                                                                                                                                                                                                                                                                    Apr 20, 2021    4 - Beta               pytest (>=3.1.1)
+   :pypi:`pytest-sosu`                              Unofficial PyTest plugin for Sauce Labs                                                                                                                                                                                                                                                                                                                                                 Aug 04, 2023    2 - Pre-Alpha          pytest
+   :pypi:`pytest-sourceorder`                       Test-ordering plugin for pytest                                                                                                                                                                                                                                                                                                                                                         Sep 01, 2021    4 - Beta               pytest
+   :pypi:`pytest-spark`                             pytest plugin to run the tests with support of pyspark.                                                                                                                                                                                                                                                                                                                                 Mar 21, 2025    4 - Beta               pytest
+   :pypi:`pytest-spawner`                           py.test plugin to spawn process and communicate with them.                                                                                                                                                                                                                                                                                                                              Jul 31, 2015    4 - Beta               N/A
+   :pypi:`pytest-spec`                              Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION.                                                                                                                                                                                                                                                                                           Aug 04, 2024    N/A                    pytest; extra == "test"
+   :pypi:`pytest-spec2md`                           Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest.                                                                                                                                                                                                                                                                                      Apr 10, 2024    N/A                    pytest>7.0
+   :pypi:`pytest-speed`                             Modern benchmarking library for python with pytest integration.                                                                                                                                                                                                                                                                                                                         Jan 22, 2023    3 - Alpha              pytest>=7
+   :pypi:`pytest-sphinx`                            Doctest plugin for pytest with support for Sphinx-specific doctest-directives                                                                                                                                                                                                                                                                                                           Apr 13, 2024    4 - Beta               pytest>=8.1.1
+   :pypi:`pytest-spiratest`                         Exports unit tests as test runs in Spira (SpiraTest/Team/Plan)                                                                                                                                                                                                                                                                                                                          Jan 01, 2024    N/A                    N/A
+   :pypi:`pytest-splinter`                          Splinter plugin for pytest testing framework                                                                                                                                                                                                                                                                                                                                            Sep 09, 2022    6 - Mature             pytest (>=3.0.0)
+   :pypi:`pytest-splinter4`                         Pytest plugin for the splinter automation library                                                                                                                                                                                                                                                                                                                                       Feb 01, 2024    6 - Mature             pytest >=8.0.0
+   :pypi:`pytest-split`                             Pytest plugin which splits the test suite to equally sized sub suites based on test execution time.                                                                                                                                                                                                                                                                                     Oct 16, 2024    4 - Beta               pytest<9,>=5
+   :pypi:`pytest-split-ext`                         Pytest plugin which splits the test suite to equally sized sub suites based on test execution time.                                                                                                                                                                                                                                                                                     Sep 23, 2023    4 - Beta               pytest (>=5,<8)
+   :pypi:`pytest-splitio`                           Split.io SDK integration for e2e tests                                                                                                                                                                                                                                                                                                                                                  Sep 22, 2020    N/A                    pytest (<7,>=5.0)
+   :pypi:`pytest-split-tests`                       A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups.                                                                                                                                                                                                                           Jul 30, 2021    5 - Production/Stable  pytest (>=2.5)
+   :pypi:`pytest-split-tests-tresorit`                                                                                                                                                                                                                                                                                                                                                                                                      Feb 22, 2021    1 - Planning           N/A
+   :pypi:`pytest-splunk-addon`                      A Dynamic test tool for Splunk Apps and Add-ons                                                                                                                                                                                                                                                                                                                                         Mar 07, 2025    N/A                    pytest<8,>5.4.0
+   :pypi:`pytest-splunk-addon-ui-smartx`            Library to support testing Splunk Add-on UX                                                                                                                                                                                                                                                                                                                                             Mar 19, 2025    N/A                    N/A
+   :pypi:`pytest-splunk-env`                        pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud                                                                                                                                                                                                                                                                                                                 Oct 22, 2020    N/A                    pytest (>=6.1.1,<7.0.0)
+   :pypi:`pytest-sqitch`                            sqitch for pytest                                                                                                                                                                                                                                                                                                                                                                       Apr 06, 2020    4 - Beta               N/A
+   :pypi:`pytest-sqlalchemy`                        pytest plugin with sqlalchemy related fixtures                                                                                                                                                                                                                                                                                                                                          Mar 13, 2018    3 - Alpha              N/A
+   :pypi:`pytest-sqlalchemy-mock`                   pytest sqlalchemy plugin for mock                                                                                                                                                                                                                                                                                                                                                       Aug 10, 2024    3 - Alpha              pytest>=7.0.0
+   :pypi:`pytest-sqlalchemy-session`                A pytest plugin for preserving test isolation that use SQLAlchemy.                                                                                                                                                                                                                                                                                                                      May 19, 2023    4 - Beta               pytest (>=7.0)
+   :pypi:`pytest-sql-bigquery`                      Yet another SQL-testing framework for BigQuery provided by pytest plugin                                                                                                                                                                                                                                                                                                                Dec 19, 2019    N/A                    pytest
+   :pypi:`pytest-sqlfluff`                          A pytest plugin to use sqlfluff to enable format checking of sql files.                                                                                                                                                                                                                                                                                                                 Dec 21, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-sqlguard`                          Pytest fixture to record and check SQL Queries made by SQLAlchemy                                                                                                                                                                                                                                                                                                                       Mar 11, 2025    4 - Beta               pytest>=7
+   :pypi:`pytest-squadcast`                         Pytest report plugin for Squadcast                                                                                                                                                                                                                                                                                                                                                      Feb 22, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest-srcpaths`                          Add paths to sys.path                                                                                                                                                                                                                                                                                                                                                                   Oct 15, 2021    N/A                    pytest>=6.2.0
+   :pypi:`pytest-ssh`                               pytest plugin for ssh command run                                                                                                                                                                                                                                                                                                                                                       May 27, 2019    N/A                    pytest
+   :pypi:`pytest-start-from`                        Start pytest run from a given point                                                                                                                                                                                                                                                                                                                                                     Apr 11, 2016    N/A                    N/A
+   :pypi:`pytest-star-track-issue`                  A package to prevent Dependency Confusion attacks against Yandex.                                                                                                                                                                                                                                                                                                                       Feb 20, 2024    N/A                    N/A
+   :pypi:`pytest-static`                            pytest-static                                                                                                                                                                                                                                                                                                                                                                           Oct 20, 2024    1 - Planning           pytest<8.0.0,>=7.4.3
+   :pypi:`pytest-stats`                             Collects tests metadata for future analysis, easy to extend for any data store                                                                                                                                                                                                                                                                                                          Jul 18, 2024    N/A                    pytest>=8.0.0
+   :pypi:`pytest-statsd`                            pytest plugin for reporting to graphite                                                                                                                                                                                                                                                                                                                                                 Nov 30, 2018    5 - Production/Stable  pytest (>=3.0.0)
+   :pypi:`pytest-status`                            Add status mark for tests                                                                                                                                                                                                                                                                                                                                                               Aug 22, 2024    N/A                    pytest
+   :pypi:`pytest-stepfunctions`                     A small description                                                                                                                                                                                                                                                                                                                                                                     May 08, 2021    4 - Beta               pytest
+   :pypi:`pytest-steps`                             Create step-wise / incremental tests in pytest.                                                                                                                                                                                                                                                                                                                                         Sep 23, 2021    5 - Production/Stable  N/A
+   :pypi:`pytest-stepwise`                          Run a test suite one failing test at a time.                                                                                                                                                                                                                                                                                                                                            Dec 01, 2015    4 - Beta               N/A
+   :pypi:`pytest-stf`                               pytest plugin for openSTF                                                                                                                                                                                                                                                                                                                                                               Sep 24, 2024    N/A                    pytest>=5.0
+   :pypi:`pytest-stochastics`                       pytest plugin that allows selectively running tests several times and accepting \*some\* failures.                                                                                                                                                                                                                                                                                      Dec 01, 2024    N/A                    pytest<9.0.0,>=8.0.0
+   :pypi:`pytest-stoq`                              A plugin to pytest stoq                                                                                                                                                                                                                                                                                                                                                                 Feb 09, 2021    4 - Beta               N/A
+   :pypi:`pytest-store`                             Pytest plugin to store values from test runs                                                                                                                                                                                                                                                                                                                                            Sep 04, 2024    3 - Alpha              pytest>=7.0.0
+   :pypi:`pytest-stress`                            A Pytest plugin that allows you to loop tests for a user defined amount of time.                                                                                                                                                                                                                                                                                                        Dec 07, 2019    4 - Beta               pytest (>=3.6.0)
+   :pypi:`pytest-structlog`                         Structured logging assertions                                                                                                                                                                                                                                                                                                                                                           Jul 25, 2024    N/A                    pytest
+   :pypi:`pytest-structmpd`                         provide structured temporary directory                                                                                                                                                                                                                                                                                                                                                  Oct 17, 2018    N/A                    N/A
+   :pypi:`pytest-stub`                              Stub packages, modules and attributes.                                                                                                                                                                                                                                                                                                                                                  Apr 28, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-stubprocess`                       Provide stub implementations for subprocesses in Python tests                                                                                                                                                                                                                                                                                                                           Sep 17, 2018    3 - Alpha              pytest (>=3.5.0)
+   :pypi:`pytest-study`                             A pytest plugin to organize long run tests (named studies) without interfering the regular tests                                                                                                                                                                                                                                                                                        Sep 26, 2017    3 - Alpha              pytest (>=2.0)
+   :pypi:`pytest-subinterpreter`                    Run pytest in a subinterpreter                                                                                                                                                                                                                                                                                                                                                          Nov 25, 2023    N/A                    pytest>=7.0.0
+   :pypi:`pytest-subprocess`                        A plugin to fake subprocess for pytest                                                                                                                                                                                                                                                                                                                                                  Jan 04, 2025    5 - Production/Stable  pytest>=4.0.0
+   :pypi:`pytest-subtesthack`                       A hack to explicitly set up and tear down fixtures.                                                                                                                                                                                                                                                                                                                                     Jul 16, 2022    N/A                    N/A
+   :pypi:`pytest-subtests`                          unittest subTest() support and subtests fixture                                                                                                                                                                                                                                                                                                                                         Dec 10, 2024    4 - Beta               pytest>=7.4
+   :pypi:`pytest-subunit`                           pytest-subunit is a plugin for py.test which outputs testsresult in subunit format.                                                                                                                                                                                                                                                                                                     Sep 17, 2023    N/A                    pytest (>=2.3)
+   :pypi:`pytest-sugar`                             pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly).                                                                                                                                                                                                                                                Feb 01, 2024    4 - Beta               pytest >=6.2.0
+   :pypi:`pytest-suitemanager`                      A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Apr 28, 2023    4 - Beta               N/A
+   :pypi:`pytest-suite-timeout`                     A pytest plugin for ensuring max suite time                                                                                                                                                                                                                                                                                                                                             Jan 26, 2024    N/A                    pytest>=7.0.0
+   :pypi:`pytest-supercov`                          Pytest plugin for measuring explicit test-file to source-file coverage                                                                                                                                                                                                                                                                                                                  Jul 02, 2023    N/A                    N/A
+   :pypi:`pytest-svn`                               SVN repository fixture for py.test                                                                                                                                                                                                                                                                                                                                                      Oct 17, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-symbols`                           pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests.                                                                                                                                                                                                                                                                             Nov 20, 2017    3 - Alpha              N/A
+   :pypi:`pytest-system-statistics`                 Pytest plugin to track and report system usage statistics                                                                                                                                                                                                                                                                                                                               Feb 16, 2022    5 - Production/Stable  pytest (>=6.0.0)
+   :pypi:`pytest-system-test-plugin`                Pyst - Pytest System-Test Plugin                                                                                                                                                                                                                                                                                                                                                        Feb 03, 2022    N/A                    N/A
+   :pypi:`pytest_tagging`                           a pytest plugin to tag tests                                                                                                                                                                                                                                                                                                                                                            Nov 08, 2024    N/A                    pytest>=7.1.3
+   :pypi:`pytest-takeltest`                         Fixtures for ansible, testinfra and molecule                                                                                                                                                                                                                                                                                                                                            Sep 07, 2024    N/A                    N/A
+   :pypi:`pytest-talisker`                                                                                                                                                                                                                                                                                                                                                                                                                  Nov 28, 2021    N/A                    N/A
+   :pypi:`pytest-tally`                             A Pytest plugin to generate realtime summary stats, and display them in-console using a text-based dashboard.                                                                                                                                                                                                                                                                           May 22, 2023    4 - Beta               pytest (>=6.2.5)
+   :pypi:`pytest-tap`                               Test Anything Protocol (TAP) reporting plugin for pytest                                                                                                                                                                                                                                                                                                                                Jan 30, 2025    5 - Production/Stable  pytest>=3.0
+   :pypi:`pytest-tape`                              easy assertion with expected results saved to yaml files                                                                                                                                                                                                                                                                                                                                Mar 17, 2021    4 - Beta               N/A
+   :pypi:`pytest-target`                            Pytest plugin for remote target orchestration.                                                                                                                                                                                                                                                                                                                                          Jan 21, 2021    3 - Alpha              pytest (>=6.1.2,<7.0.0)
+   :pypi:`pytest-taskgraph`                         Add your description here                                                                                                                                                                                                                                                                                                                                                               Dec 12, 2024    N/A                    pytest
+   :pypi:`pytest-tblineinfo`                        tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used                                                                                                                                                                                                                                                                        Dec 01, 2015    3 - Alpha              pytest (>=2.0)
+   :pypi:`pytest-tcpclient`                         A pytest plugin for testing TCP clients                                                                                                                                                                                                                                                                                                                                                 Nov 16, 2022    N/A                    pytest (<8,>=7.1.3)
+   :pypi:`pytest-tdd`                               run pytest on a python module                                                                                                                                                                                                                                                                                                                                                           Aug 18, 2023    4 - Beta               N/A
+   :pypi:`pytest-teamcity-logblock`                 py.test plugin to introduce block structure in teamcity build log, if output is not captured                                                                                                                                                                                                                                                                                            May 15, 2018    4 - Beta               N/A
+   :pypi:`pytest-teardown`                                                                                                                                                                                                                                                                                                                                                                                                                  Feb 03, 2025    N/A                    pytest<9.0.0,>=7.4.1
+   :pypi:`pytest-telegram`                          Pytest to Telegram reporting plugin                                                                                                                                                                                                                                                                                                                                                     Apr 25, 2024    5 - Production/Stable  N/A
+   :pypi:`pytest-telegram-notifier`                 Telegram notification plugin for Pytest                                                                                                                                                                                                                                                                                                                                                 Jun 27, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-tempdir`                           Predictable and repeatable tempdir support.                                                                                                                                                                                                                                                                                                                                             Oct 11, 2019    4 - Beta               pytest (>=2.8.1)
+   :pypi:`pytest-terra-fixt`                        Terraform and Terragrunt fixtures for pytest                                                                                                                                                                                                                                                                                                                                            Sep 15, 2022    N/A                    pytest (==6.2.5)
+   :pypi:`pytest-terraform`                         A pytest plugin for using terraform fixtures                                                                                                                                                                                                                                                                                                                                            May 21, 2024    N/A                    pytest>=6.0
+   :pypi:`pytest-terraform-fixture`                 generate terraform resources to use with pytest                                                                                                                                                                                                                                                                                                                                         Nov 14, 2018    4 - Beta               N/A
+   :pypi:`pytest-testbook`                          A plugin to run tests written in Jupyter notebook                                                                                                                                                                                                                                                                                                                                       Dec 11, 2016    3 - Alpha              N/A
+   :pypi:`pytest-testconfig`                        Test configuration plugin for pytest.                                                                                                                                                                                                                                                                                                                                                   Jan 11, 2020    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-testdata`                          Get and load testdata in pytest projects                                                                                                                                                                                                                                                                                                                                                Aug 30, 2024    N/A                    pytest
+   :pypi:`pytest-testdirectory`                     A py.test plugin providing temporary directories in unit tests.                                                                                                                                                                                                                                                                                                                         May 02, 2023    5 - Production/Stable  pytest
+   :pypi:`pytest-testdox`                           A testdox format reporter for pytest                                                                                                                                                                                                                                                                                                                                                    Jul 22, 2023    5 - Production/Stable  pytest (>=4.6.0)
+   :pypi:`pytest-test-grouping`                     A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups.                                                                                                                                                                                                                                                                                        Feb 01, 2023    5 - Production/Stable  pytest (>=2.5)
+   :pypi:`pytest-test-groups`                       A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups.                                                                                                                                                                                                                                                                                        Mar 28, 2025    5 - Production/Stable  pytest>=7.0.0
+   :pypi:`pytest-testinfra`                         Test infrastructures                                                                                                                                                                                                                                                                                                                                                                    Mar 30, 2025    5 - Production/Stable  pytest>=6
+   :pypi:`pytest-testinfra-jpic`                    Test infrastructures                                                                                                                                                                                                                                                                                                                                                                    Sep 21, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-testinfra-winrm-transport`         Test infrastructures                                                                                                                                                                                                                                                                                                                                                                    Sep 21, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-testit-parametrize`                A pytest plugin for uploading parameterized tests parameters into TMS TestIT                                                                                                                                                                                                                                                                                                            Dec 04, 2024    4 - Beta               pytest>=8.3.3
+   :pypi:`pytest-testlink-adaptor`                  pytest reporting plugin for testlink                                                                                                                                                                                                                                                                                                                                                    Dec 20, 2018    4 - Beta               pytest (>=2.6)
+   :pypi:`pytest-testmon`                           selects tests affected by changed files and methods                                                                                                                                                                                                                                                                                                                                     Dec 22, 2024    4 - Beta               pytest<9,>=5
+   :pypi:`pytest-testmon-dev`                       selects tests affected by changed files and methods                                                                                                                                                                                                                                                                                                                                     Mar 30, 2023    4 - Beta               pytest (<8,>=5)
+   :pypi:`pytest-testmon-oc`                        nOly selects tests affected by changed files and methods                                                                                                                                                                                                                                                                                                                                Jun 01, 2022    4 - Beta               pytest (<8,>=5)
+   :pypi:`pytest-testmon-skip-libraries`            selects tests affected by changed files and methods                                                                                                                                                                                                                                                                                                                                     Mar 03, 2023    4 - Beta               pytest (<8,>=5)
+   :pypi:`pytest-testobject`                        Plugin to use TestObject Suites with Pytest                                                                                                                                                                                                                                                                                                                                             Sep 24, 2019    4 - Beta               pytest (>=3.1.1)
+   :pypi:`pytest-testpluggy`                        set your encoding                                                                                                                                                                                                                                                                                                                                                                       Jan 07, 2022    N/A                    pytest
+   :pypi:`pytest-testrail`                          pytest plugin for creating TestRail runs and adding results                                                                                                                                                                                                                                                                                                                             Aug 27, 2020    N/A                    pytest (>=3.6)
+   :pypi:`pytest-testrail2`                         A pytest plugin to upload results to TestRail.                                                                                                                                                                                                                                                                                                                                          Feb 10, 2023    N/A                    pytest (<8.0,>=7.2.0)
+   :pypi:`pytest-testrail-api`                      TestRail Api Python Client                                                                                                                                                                                                                                                                                                                                                              Mar 17, 2025    N/A                    pytest
+   :pypi:`pytest-testrail-api-client`               TestRail Api Python Client                                                                                                                                                                                                                                                                                                                                                              Dec 14, 2021    N/A                    pytest
+   :pypi:`pytest-testrail-appetize`                 pytest plugin for creating TestRail runs and adding results                                                                                                                                                                                                                                                                                                                             Sep 29, 2021    N/A                    N/A
+   :pypi:`pytest-testrail-client`                   pytest plugin for Testrail                                                                                                                                                                                                                                                                                                                                                              Sep 29, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-testrail-e2e`                      pytest plugin for creating TestRail runs and adding results                                                                                                                                                                                                                                                                                                                             Oct 11, 2021    N/A                    pytest (>=3.6)
+   :pypi:`pytest-testrail-integrator`               Pytest plugin for sending report to testrail system.                                                                                                                                                                                                                                                                                                                                    Aug 01, 2022    N/A                    pytest (>=6.2.5)
+   :pypi:`pytest-testrail-ns`                       pytest plugin for creating TestRail runs and adding results                                                                                                                                                                                                                                                                                                                             Aug 12, 2022    N/A                    N/A
+   :pypi:`pytest-testrail-plugin`                   PyTest plugin for TestRail                                                                                                                                                                                                                                                                                                                                                              Apr 21, 2020    3 - Alpha              pytest
+   :pypi:`pytest-testrail-reporter`                                                                                                                                                                                                                                                                                                                                                                                                         Sep 10, 2018    N/A                    N/A
+   :pypi:`pytest-testrail-results`                  A pytest plugin to upload results to TestRail.                                                                                                                                                                                                                                                                                                                                          Mar 04, 2024    N/A                    pytest >=7.2.0
+   :pypi:`pytest-testreport`                                                                                                                                                                                                                                                                                                                                                                                                                Dec 01, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-testreport-new`                                                                                                                                                                                                                                                                                                                                                                                                            Oct 07, 2023    4 - Beta               pytest >=3.5.0
+   :pypi:`pytest-testslide`                         TestSlide fixture for pytest                                                                                                                                                                                                                                                                                                                                                            Jan 07, 2021    5 - Production/Stable  pytest (~=6.2)
+   :pypi:`pytest-test-this`                         Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply                                                                                                                                                                                                                                                             Sep 15, 2019    2 - Pre-Alpha          pytest (>=2.3)
+   :pypi:`pytest-test-tracer-for-pytest`            A plugin that allows coll test data for use on Test Tracer                                                                                                                                                                                                                                                                                                                              Jun 28, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-test-tracer-for-pytest-bdd`        A plugin that allows coll test data for use on Test Tracer                                                                                                                                                                                                                                                                                                                              Aug 20, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-test-utils`                                                                                                                                                                                                                                                                                                                                                                                                                Feb 08, 2024    N/A                    pytest >=3.9
+   :pypi:`pytest-tesults`                           Tesults plugin for pytest                                                                                                                                                                                                                                                                                                                                                               Nov 12, 2024    5 - Production/Stable  pytest>=3.5.0
+   :pypi:`pytest-textual-snapshot`                  Snapshot testing for Textual apps                                                                                                                                                                                                                                                                                                                                                       Jan 23, 2025    5 - Production/Stable  pytest>=8.0.0
+   :pypi:`pytest-tezos`                             pytest-ligo                                                                                                                                                                                                                                                                                                                                                                             Jan 16, 2020    4 - Beta               N/A
+   :pypi:`pytest-tf`                                Test your OpenTofu and Terraform config using a PyTest plugin                                                                                                                                                                                                                                                                                                                           May 29, 2024    N/A                    pytest<9.0.0,>=8.2.1
+   :pypi:`pytest-th2-bdd`                           pytest_th2_bdd                                                                                                                                                                                                                                                                                                                                                                          May 13, 2022    N/A                    N/A
+   :pypi:`pytest-thawgun`                           Pytest plugin for time travel                                                                                                                                                                                                                                                                                                                                                           May 26, 2020    3 - Alpha              N/A
+   :pypi:`pytest-thread`                                                                                                                                                                                                                                                                                                                                                                                                                    Jul 07, 2023    N/A                    N/A
+   :pypi:`pytest-threadleak`                        Detects thread leaks                                                                                                                                                                                                                                                                                                                                                                    Jul 03, 2022    4 - Beta               pytest (>=3.1.1)
+   :pypi:`pytest-tick`                              Ticking on tests                                                                                                                                                                                                                                                                                                                                                                        Aug 31, 2021    5 - Production/Stable  pytest (>=6.2.5,<7.0.0)
+   :pypi:`pytest-time`                                                                                                                                                                                                                                                                                                                                                                                                                      Jan 20, 2025    3 - Alpha              pytest
+   :pypi:`pytest-timeassert-ethan`                  execution duration                                                                                                                                                                                                                                                                                                                                                                      Dec 25, 2023    N/A                    pytest
+   :pypi:`pytest-timeit`                            A pytest plugin to time test function runs                                                                                                                                                                                                                                                                                                                                              Oct 13, 2016    4 - Beta               N/A
+   :pypi:`pytest-timeout`                           pytest plugin to abort hanging tests                                                                                                                                                                                                                                                                                                                                                    Mar 07, 2024    5 - Production/Stable  pytest >=7.0.0
+   :pypi:`pytest-timeouts`                          Linux-only Pytest plugin to control durations of various test case execution phases                                                                                                                                                                                                                                                                                                     Sep 21, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-timer`                             A timer plugin for pytest                                                                                                                                                                                                                                                                                                                                                               Dec 26, 2023    N/A                    pytest
+   :pypi:`pytest-timestamper`                       Pytest plugin to add a timestamp prefix to the pytest output                                                                                                                                                                                                                                                                                                                            Mar 27, 2024    N/A                    N/A
+   :pypi:`pytest-timestamps`                        A simple plugin to view timestamps for each test                                                                                                                                                                                                                                                                                                                                        Sep 11, 2023    N/A                    pytest (>=7.3,<8.0)
+   :pypi:`pytest-tiny-api-client`                   The companion pytest plugin for tiny-api-client                                                                                                                                                                                                                                                                                                                                         Jan 04, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-tinybird`                          A pytest plugin to report test results to tinybird                                                                                                                                                                                                                                                                                                                                      Feb 18, 2025    4 - Beta               pytest>=3.8.0
+   :pypi:`pytest-tipsi-django`                      Better fixtures for django                                                                                                                                                                                                                                                                                                                                                              Feb 05, 2024    5 - Production/Stable  pytest>=6.0.0
+   :pypi:`pytest-tipsi-testing`                     Better fixtures management. Various helpers                                                                                                                                                                                                                                                                                                                                             Feb 04, 2024    5 - Production/Stable  pytest>=3.3.0
+   :pypi:`pytest-tldr`                              A pytest plugin that limits the output to just the things you need.                                                                                                                                                                                                                                                                                                                     Oct 26, 2022    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-tm4j-reporter`                     Cloud Jira Test Management (TM4J) PyTest reporter plugin                                                                                                                                                                                                                                                                                                                                Sep 01, 2020    N/A                    pytest
+   :pypi:`pytest-tmnet`                             A small example package                                                                                                                                                                                                                                                                                                                                                                 Mar 01, 2022    N/A                    N/A
+   :pypi:`pytest-tmp-files`                         Utilities to create temporary file hierarchies in pytest.                                                                                                                                                                                                                                                                                                                               Dec 08, 2023    N/A                    pytest
+   :pypi:`pytest-tmpfs`                             A pytest plugin that helps you on using a temporary filesystem for testing.                                                                                                                                                                                                                                                                                                             Aug 29, 2022    N/A                    pytest
+   :pypi:`pytest-tmreport`                          this is a vue-element ui report for pytest                                                                                                                                                                                                                                                                                                                                              Aug 12, 2022    N/A                    N/A
+   :pypi:`pytest-tmux`                              A pytest plugin that enables tmux driven tests                                                                                                                                                                                                                                                                                                                                          Apr 22, 2023    4 - Beta               N/A
+   :pypi:`pytest-todo`                              A small plugin for the pytest testing framework, marking TODO comments as failure                                                                                                                                                                                                                                                                                                       May 23, 2019    4 - Beta               pytest
+   :pypi:`pytest-tomato`                                                                                                                                                                                                                                                                                                                                                                                                                    Mar 01, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-toolbelt`                          This is just a collection of utilities for pytest, but don't really belong in pytest proper.                                                                                                                                                                                                                                                                                            Aug 12, 2019    3 - Alpha              N/A
+   :pypi:`pytest-toolbox`                           Numerous useful plugins for pytest.                                                                                                                                                                                                                                                                                                                                                     Apr 07, 2018    N/A                    pytest (>=3.5.0)
+   :pypi:`pytest-toolkit`                           Useful utils for testing                                                                                                                                                                                                                                                                                                                                                                Jun 07, 2024    N/A                    N/A
+   :pypi:`pytest-tools`                             Pytest tools                                                                                                                                                                                                                                                                                                                                                                            Oct 21, 2022    4 - Beta               N/A
+   :pypi:`pytest-topo`                              Topological sorting for pytest                                                                                                                                                                                                                                                                                                                                                          Jun 05, 2024    N/A                    pytest>=7.0.0
+   :pypi:`pytest-tornado`                           A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications.                                                                                                                                                                                                                                                                               Jun 17, 2020    5 - Production/Stable  pytest (>=3.6)
+   :pypi:`pytest-tornado5`                          A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications.                                                                                                                                                                                                                                                                               Nov 16, 2018    5 - Production/Stable  pytest (>=3.6)
+   :pypi:`pytest-tornado-yen3`                      A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications.                                                                                                                                                                                                                                                                               Oct 15, 2018    5 - Production/Stable  N/A
+   :pypi:`pytest-tornasync`                         py.test plugin for testing Python 3.5+ Tornado code                                                                                                                                                                                                                                                                                                                                     Jul 15, 2019    3 - Alpha              pytest (>=3.0)
+   :pypi:`pytest-trace`                             Save OpenTelemetry spans generated during testing                                                                                                                                                                                                                                                                                                                                       Jun 19, 2022    N/A                    pytest (>=4.6)
+   :pypi:`pytest-track`                                                                                                                                                                                                                                                                                                                                                                                                                     Feb 26, 2021    3 - Alpha              pytest (>=3.0)
+   :pypi:`pytest-translations`                      Test your translation files.                                                                                                                                                                                                                                                                                                                                                            Sep 11, 2023    5 - Production/Stable  pytest (>=7)
+   :pypi:`pytest-travis-fold`                       Folds captured output sections in Travis CI build log                                                                                                                                                                                                                                                                                                                                   Nov 29, 2017    4 - Beta               pytest (>=2.6.0)
+   :pypi:`pytest-trello`                            Plugin for py.test that integrates trello using markers                                                                                                                                                                                                                                                                                                                                 Nov 20, 2015    5 - Production/Stable  N/A
+   :pypi:`pytest-trepan`                            Pytest plugin for trepan debugger.                                                                                                                                                                                                                                                                                                                                                      Jul 28, 2018    5 - Production/Stable  N/A
+   :pypi:`pytest-trialtemp`                         py.test plugin for using the same _trial_temp working directory as trial                                                                                                                                                                                                                                                                                                                Jun 08, 2015    N/A                    N/A
+   :pypi:`pytest-trio`                              Pytest plugin for trio                                                                                                                                                                                                                                                                                                                                                                  Nov 01, 2022    N/A                    pytest (>=7.2.0)
+   :pypi:`pytest-trytond`                           Pytest plugin for the Tryton server framework                                                                                                                                                                                                                                                                                                                                           Nov 04, 2022    4 - Beta               pytest (>=5)
+   :pypi:`pytest-tspwplib`                          A simple plugin to use with tspwplib                                                                                                                                                                                                                                                                                                                                                    Jan 08, 2021    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-tst`                               Customize pytest options, output and exit code to make it compatible with tst                                                                                                                                                                                                                                                                                                           Apr 27, 2022    N/A                    pytest (>=5.0.0)
+   :pypi:`pytest-tstcls`                            Test Class Base                                                                                                                                                                                                                                                                                                                                                                         Mar 23, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-tui`                               Text User Interface (TUI) and HTML report for Pytest test runs                                                                                                                                                                                                                                                                                                                          Dec 08, 2023    4 - Beta               N/A
+   :pypi:`pytest-tutorials`                                                                                                                                                                                                                                                                                                                                                                                                                 Mar 11, 2023    N/A                    N/A
+   :pypi:`pytest-twilio-conversations-client-mock`                                                                                                                                                                                                                                                                                                                                                                                          Aug 02, 2022    N/A                    N/A
+   :pypi:`pytest-twisted`                           A twisted plugin for pytest.                                                                                                                                                                                                                                                                                                                                                            Sep 10, 2024    5 - Production/Stable  pytest>=2.3
+   :pypi:`pytest-typechecker`                       Run type checkers on specified test files                                                                                                                                                                                                                                                                                                                                               Feb 04, 2022    N/A                    pytest (>=6.2.5,<7.0.0)
+   :pypi:`pytest-typhoon-config`                    A Typhoon HIL plugin that facilitates test parameter configuration at runtime                                                                                                                                                                                                                                                                                                           Apr 07, 2022    5 - Production/Stable  N/A
+   :pypi:`pytest-typhoon-polarion`                  Typhoontest plugin for Siemens Polarion                                                                                                                                                                                                                                                                                                                                                 Feb 01, 2024    4 - Beta               N/A
+   :pypi:`pytest-typhoon-xray`                      Typhoon HIL plugin for pytest                                                                                                                                                                                                                                                                                                                                                           Aug 15, 2023    4 - Beta               N/A
+   :pypi:`pytest-typing-runner`                     Pytest plugin to make it easier to run and check python code against static type checkers                                                                                                                                                                                                                                                                                               Oct 27, 2024    N/A                    N/A
+   :pypi:`pytest-tytest`                            Typhoon HIL plugin for pytest                                                                                                                                                                                                                                                                                                                                                           May 25, 2020    4 - Beta               pytest (>=5.4.2)
+   :pypi:`pytest-ubersmith`                         Easily mock calls to ubersmith at the \`requests\` level.                                                                                                                                                                                                                                                                                                                               Apr 13, 2015    N/A                    N/A
+   :pypi:`pytest-ui`                                Text User Interface for running python tests                                                                                                                                                                                                                                                                                                                                            Jul 05, 2021    4 - Beta               pytest
+   :pypi:`pytest-ui-failed-screenshot`              UI自动测试失败时自动截图,并将截图加入到测试报告中                                                                                                                                                                                                                                                                                                                                      Dec 06, 2022    N/A                    N/A
+   :pypi:`pytest-ui-failed-screenshot-allure`       UI自动测试失败时自动截图,并将截图加入到Allure测试报告中                                                                                                                                                                                                                                                                                                                                Dec 06, 2022    N/A                    N/A
+   :pypi:`pytest-uncollect-if`                      A plugin to uncollect pytests tests rather than using skipif                                                                                                                                                                                                                                                                                                                            Dec 26, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-unflakable`                        Unflakable plugin for PyTest                                                                                                                                                                                                                                                                                                                                                            Apr 30, 2024    4 - Beta               pytest>=6.2.0
+   :pypi:`pytest-unhandled-exception-exit-code`     Plugin for py.test set a different exit code on uncaught exceptions                                                                                                                                                                                                                                                                                                                     Jun 22, 2020    5 - Production/Stable  pytest (>=2.3)
+   :pypi:`pytest-unique`                            Pytest fixture to generate unique values.                                                                                                                                                                                                                                                                                                                                               Mar 23, 2025    N/A                    pytest<8.0.0,>=7.4.2
+   :pypi:`pytest-unittest-filter`                   A pytest plugin for filtering unittest-based test classes                                                                                                                                                                                                                                                                                                                               Jan 12, 2019    4 - Beta               pytest (>=3.1.0)
+   :pypi:`pytest-unittest-id-runner`                A pytest plugin to run tests using unittest-style test IDs                                                                                                                                                                                                                                                                                                                              Feb 09, 2025    N/A                    pytest>=6.0.0
+   :pypi:`pytest-unmagic`                           Pytest fixtures with conventional import semantics                                                                                                                                                                                                                                                                                                                                      Oct 22, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-unmarked`                          Run only unmarked tests                                                                                                                                                                                                                                                                                                                                                                 Aug 27, 2019    5 - Production/Stable  N/A
+   :pypi:`pytest-unordered`                         Test equality of unordered collections in pytest                                                                                                                                                                                                                                                                                                                                        Jul 05, 2024    4 - Beta               pytest>=7.0.0
+   :pypi:`pytest-unstable`                          Set a test as unstable to return 0 even if it failed                                                                                                                                                                                                                                                                                                                                    Sep 27, 2022    4 - Beta               N/A
+   :pypi:`pytest-unused-fixtures`                   A pytest plugin to list unused fixtures after a test run.                                                                                                                                                                                                                                                                                                                               Mar 15, 2025    4 - Beta               pytest>7.3.2
+   :pypi:`pytest-upload-report`                     pytest-upload-report is a plugin for pytest that upload your test report for test results.                                                                                                                                                                                                                                                                                              Jun 18, 2021    5 - Production/Stable  N/A
+   :pypi:`pytest-utils`                             Some helpers for pytest.                                                                                                                                                                                                                                                                                                                                                                Feb 02, 2023    4 - Beta               pytest (>=7.0.0,<8.0.0)
+   :pypi:`pytest-vagrant`                           A py.test plugin providing access to vagrant.                                                                                                                                                                                                                                                                                                                                           Sep 07, 2021    5 - Production/Stable  pytest
+   :pypi:`pytest-valgrind`                                                                                                                                                                                                                                                                                                                                                                                                                  May 19, 2021    N/A                    N/A
+   :pypi:`pytest-variables`                         pytest plugin for providing variables to tests/fixtures                                                                                                                                                                                                                                                                                                                                 Feb 01, 2024    5 - Production/Stable  pytest>=7.0.0
+   :pypi:`pytest-variant`                           Variant support for Pytest                                                                                                                                                                                                                                                                                                                                                              Jun 06, 2022    N/A                    N/A
+   :pypi:`pytest-vcr`                               Plugin for managing VCR.py cassettes                                                                                                                                                                                                                                                                                                                                                    Apr 26, 2019    5 - Production/Stable  pytest (>=3.6.0)
+   :pypi:`pytest-vcr-delete-on-fail`                A pytest plugin that automates vcrpy cassettes deletion on test failure.                                                                                                                                                                                                                                                                                                                Feb 16, 2024    5 - Production/Stable  pytest (>=8.0.0,<9.0.0)
+   :pypi:`pytest-vcrpandas`                         Test from HTTP interactions to dataframe processed.                                                                                                                                                                                                                                                                                                                                     Jan 12, 2019    4 - Beta               pytest
+   :pypi:`pytest-vcs`                                                                                                                                                                                                                                                                                                                                                                                                                       Sep 22, 2022    4 - Beta               N/A
+   :pypi:`pytest-venv`                              py.test fixture for creating a virtual environment                                                                                                                                                                                                                                                                                                                                      Nov 23, 2023    4 - Beta               pytest
+   :pypi:`pytest-verbose-parametrize`               More descriptive output for parametrized py.test tests                                                                                                                                                                                                                                                                                                                                  Nov 29, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-vimqf`                             A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window.                                                                                                                                                                                                                                                                                       Feb 08, 2021    4 - Beta               pytest (>=6.2.2,<7.0.0)
+   :pypi:`pytest-virtualenv`                        Virtualenv fixture for py.test                                                                                                                                                                                                                                                                                                                                                          Nov 29, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-visual`                                                                                                                                                                                                                                                                                                                                                                                                                    Nov 28, 2024    4 - Beta               pytest>=7.0.0
+   :pypi:`pytest-vnc`                               VNC client for Pytest                                                                                                                                                                                                                                                                                                                                                                   Nov 06, 2023    N/A                    pytest
+   :pypi:`pytest-voluptuous`                        Pytest plugin for asserting data against voluptuous schema.                                                                                                                                                                                                                                                                                                                             Jun 09, 2020    N/A                    pytest
+   :pypi:`pytest-vscodedebug`                       A pytest plugin to easily enable debugging tests within Visual Studio Code                                                                                                                                                                                                                                                                                                              Dec 04, 2020    4 - Beta               N/A
+   :pypi:`pytest-vscode-pycharm-cls`                A PyTest helper to enable start remote debugger on test start or failure or when pytest.set_trace is used.                                                                                                                                                                                                                                                                              Feb 01, 2023    N/A                    pytest
+   :pypi:`pytest-vtestify`                          A pytest plugin for visual assertion using SSIM and image comparison.                                                                                                                                                                                                                                                                                                                   Feb 04, 2025    N/A                    pytest
+   :pypi:`pytest-vts`                               pytest plugin for automatic recording of http stubbed tests                                                                                                                                                                                                                                                                                                                             Jun 05, 2019    N/A                    pytest (>=2.3)
+   :pypi:`pytest-vulture`                           A pytest plugin to checks dead code with vulture                                                                                                                                                                                                                                                                                                                                        Nov 25, 2024    N/A                    pytest>=7.0.0
+   :pypi:`pytest-vw`                                pytest-vw makes your failing test cases succeed under CI tools scrutiny                                                                                                                                                                                                                                                                                                                 Oct 07, 2015    4 - Beta               N/A
+   :pypi:`pytest-vyper`                             Plugin for the vyper smart contract language.                                                                                                                                                                                                                                                                                                                                           May 28, 2020    2 - Pre-Alpha          N/A
+   :pypi:`pytest-wa-e2e-plugin`                     Pytest plugin for testing whatsapp bots with end to end tests                                                                                                                                                                                                                                                                                                                           Feb 18, 2020    4 - Beta               pytest (>=3.5.0)
+   :pypi:`pytest-wake`                                                                                                                                                                                                                                                                                                                                                                                                                      Nov 19, 2024    N/A                    pytest
+   :pypi:`pytest-watch`                             Local continuous test runner with pytest and watchdog.                                                                                                                                                                                                                                                                                                                                  May 20, 2018    N/A                    N/A
+   :pypi:`pytest-watcher`                           Automatically rerun your tests on file modifications                                                                                                                                                                                                                                                                                                                                    Aug 28, 2024    4 - Beta               N/A
+   :pypi:`pytest-watch-plugin`                      Placeholder for internal package                                                                                                                                                                                                                                                                                                                                                        Sep 12, 2024    N/A                    N/A
+   :pypi:`pytest_wdb`                               Trace pytest tests with wdb to halt on error with --wdb.                                                                                                                                                                                                                                                                                                                                Jul 04, 2016    N/A                    N/A
+   :pypi:`pytest-wdl`                               Pytest plugin for testing WDL workflows.                                                                                                                                                                                                                                                                                                                                                Nov 17, 2020    5 - Production/Stable  N/A
+   :pypi:`pytest-web3-data`                         A pytest plugin to fetch test data from IPFS HTTP gateways during pytest execution.                                                                                                                                                                                                                                                                                                     Oct 04, 2023    4 - Beta               pytest
+   :pypi:`pytest-webdriver`                         Selenium webdriver fixture for py.test                                                                                                                                                                                                                                                                                                                                                  Oct 17, 2024    5 - Production/Stable  pytest
+   :pypi:`pytest-webstage`                          Test web apps with pytest                                                                                                                                                                                                                                                                                                                                                               Sep 20, 2024    N/A                    pytest<9.0,>=7.0
+   :pypi:`pytest-webtest-extras`                    Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources.                                                                                                                                                                                                                                                        Dec 28, 2024    N/A                    pytest>=7.0.0
+   :pypi:`pytest-wetest`                            Welian API Automation test framework pytest plugin                                                                                                                                                                                                                                                                                                                                      Nov 10, 2018    4 - Beta               N/A
+   :pypi:`pytest-when`                              Utility which makes mocking more readable and controllable                                                                                                                                                                                                                                                                                                                              Nov 29, 2024    N/A                    pytest>=7.3.1
+   :pypi:`pytest-whirlwind`                         Testing Tornado.                                                                                                                                                                                                                                                                                                                                                                        Jun 12, 2020    N/A                    N/A
+   :pypi:`pytest-wholenodeid`                       pytest addon for displaying the whole node id for failures                                                                                                                                                                                                                                                                                                                              Aug 26, 2015    4 - Beta               pytest (>=2.0)
+   :pypi:`pytest-win32consoletitle`                 Pytest progress in console title (Win32 only)                                                                                                                                                                                                                                                                                                                                           Aug 08, 2021    N/A                    N/A
+   :pypi:`pytest-winnotify`                         Windows tray notifications for py.test results.                                                                                                                                                                                                                                                                                                                                         Apr 22, 2016    N/A                    N/A
+   :pypi:`pytest-wiremock`                          A pytest plugin for programmatically using wiremock in integration tests                                                                                                                                                                                                                                                                                                                Mar 27, 2022    N/A                    pytest (>=7.1.1,<8.0.0)
+   :pypi:`pytest-wiretap`                           \`pytest\` plugin for recording call stacks                                                                                                                                                                                                                                                                                                                                             Mar 18, 2025    N/A                    pytest
+   :pypi:`pytest-with-docker`                       pytest with docker helpers.                                                                                                                                                                                                                                                                                                                                                             Nov 09, 2021    N/A                    pytest
+   :pypi:`pytest-workaround-12888`                  forces an import of readline early in the process to work around pytest bug #12888                                                                                                                                                                                                                                                                                                      Jan 15, 2025    N/A                    N/A
+   :pypi:`pytest-workflow`                          A pytest plugin for configuring workflow/pipeline tests using YAML files                                                                                                                                                                                                                                                                                                                Mar 18, 2024    5 - Production/Stable  pytest >=7.0.0
+   :pypi:`pytest-xdist`                             pytest xdist plugin for distributed testing, most importantly across multiple CPUs                                                                                                                                                                                                                                                                                                      Apr 28, 2024    5 - Production/Stable  pytest>=7.0.0
+   :pypi:`pytest-xdist-debug-for-graingert`         pytest xdist plugin for distributed testing and loop-on-failing modes                                                                                                                                                                                                                                                                                                                   Jul 24, 2019    5 - Production/Stable  pytest (>=4.4.0)
+   :pypi:`pytest-xdist-forked`                      forked from pytest-xdist                                                                                                                                                                                                                                                                                                                                                                Feb 10, 2020    5 - Production/Stable  pytest (>=4.4.0)
+   :pypi:`pytest-xdist-tracker`                     pytest plugin helps to reproduce failures for particular xdist node                                                                                                                                                                                                                                                                                                                     Nov 18, 2021    3 - Alpha              pytest (>=3.5.1)
+   :pypi:`pytest-xdist-worker-stats`                A pytest plugin to list worker statistics after a xdist run.                                                                                                                                                                                                                                                                                                                            Mar 15, 2025    4 - Beta               pytest>=7.0.0
+   :pypi:`pytest-xdocker`                           Pytest fixture to run docker across test runs.                                                                                                                                                                                                                                                                                                                                          Mar 23, 2025    N/A                    pytest<8.0.0,>=7.4.2
+   :pypi:`pytest-xfaillist`                         Maintain a xfaillist in an additional file to avoid merge-conflicts.                                                                                                                                                                                                                                                                                                                    Sep 17, 2021    N/A                    pytest (>=6.2.2,<7.0.0)
+   :pypi:`pytest-xfiles`                            Pytest fixtures providing data read from function, module or package related (x)files.                                                                                                                                                                                                                                                                                                  Feb 27, 2018    N/A                    N/A
+   :pypi:`pytest-xflaky`                            A simple plugin to use with pytest                                                                                                                                                                                                                                                                                                                                                      Oct 14, 2024    4 - Beta               pytest>=8.2.1
+   :pypi:`pytest-xiuyu`                             This is a pytest plugin                                                                                                                                                                                                                                                                                                                                                                 Jul 25, 2023    5 - Production/Stable  N/A
+   :pypi:`pytest-xlog`                              Extended logging for test and decorators                                                                                                                                                                                                                                                                                                                                                May 31, 2020    4 - Beta               N/A
+   :pypi:`pytest-xlsx`                              pytest plugin for generating test cases by xlsx(excel)                                                                                                                                                                                                                                                                                                                                  Aug 07, 2024    N/A                    pytest~=8.2.2
+   :pypi:`pytest-xml`                               Create simple XML results for parsing                                                                                                                                                                                                                                                                                                                                                   Nov 14, 2024    4 - Beta               pytest>=8.0.0
+   :pypi:`pytest-xpara`                             An extended parametrizing plugin of pytest.                                                                                                                                                                                                                                                                                                                                             Aug 07, 2024    3 - Alpha              pytest
+   :pypi:`pytest-xprocess`                          A pytest plugin for managing processes across test runs.                                                                                                                                                                                                                                                                                                                                May 19, 2024    4 - Beta               pytest>=2.8
+   :pypi:`pytest-xray`                                                                                                                                                                                                                                                                                                                                                                                                                      May 30, 2019    3 - Alpha              N/A
+   :pypi:`pytest-xrayjira`                                                                                                                                                                                                                                                                                                                                                                                                                  Mar 17, 2020    3 - Alpha              pytest (==4.3.1)
+   :pypi:`pytest-xray-server`                                                                                                                                                                                                                                                                                                                                                                                                               May 03, 2022    3 - Alpha              pytest (>=5.3.1)
+   :pypi:`pytest-xskynet`                           A package to prevent Dependency Confusion attacks against Yandex.                                                                                                                                                                                                                                                                                                                       Feb 20, 2024    N/A                    N/A
+   :pypi:`pytest-xstress`                                                                                                                                                                                                                                                                                                                                                                                                                   Jun 01, 2024    N/A                    pytest<9.0.0,>=8.0.0
+   :pypi:`pytest-xvfb`                              A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests.                                                                                                                                                                                                                                                                                                                                 Mar 12, 2025    4 - Beta               pytest>=2.8.1
+   :pypi:`pytest-xvirt`                             A pytest plugin to virtualize test. For example to transparently running them on a remote box.                                                                                                                                                                                                                                                                                          Dec 15, 2024    4 - Beta               pytest>=7.2.2
+   :pypi:`pytest-yaml`                              This plugin is used to load yaml output to your test using pytest framework.                                                                                                                                                                                                                                                                                                            Oct 05, 2018    N/A                    pytest
+   :pypi:`pytest-yaml-fei`                          a pytest yaml allure package                                                                                                                                                                                                                                                                                                                                                            Feb 09, 2025    N/A                    pytest
+   :pypi:`pytest-yaml-sanmu`                        Pytest plugin for generating test cases with YAML. In test cases, you can use markers, fixtures, variables, and even call Python functions.                                                                                                                                                                                                                                             Jan 03, 2025    N/A                    pytest>=8.2.2
+   :pypi:`pytest-yamltree`                          Create or check file/directory trees described by YAML                                                                                                                                                                                                                                                                                                                                  Mar 02, 2020    4 - Beta               pytest (>=3.1.1)
+   :pypi:`pytest-yamlwsgi`                          Run tests against wsgi apps defined in yaml                                                                                                                                                                                                                                                                                                                                             May 11, 2010    N/A                    N/A
+   :pypi:`pytest-yaml-yoyo`                         http/https API run by yaml                                                                                                                                                                                                                                                                                                                                                              Jun 19, 2023    N/A                    pytest (>=7.2.0)
+   :pypi:`pytest-yapf`                              Run yapf                                                                                                                                                                                                                                                                                                                                                                                Jul 06, 2017    4 - Beta               pytest (>=3.1.1)
+   :pypi:`pytest-yapf3`                             Validate your Python file format with yapf                                                                                                                                                                                                                                                                                                                                              Mar 29, 2023    5 - Production/Stable  pytest (>=7)
+   :pypi:`pytest-yield`                             PyTest plugin to run tests concurrently, each \`yield\` switch context to other one                                                                                                                                                                                                                                                                                                     Jan 23, 2019    N/A                    N/A
+   :pypi:`pytest-yls`                               Pytest plugin to test the YLS as a whole.                                                                                                                                                                                                                                                                                                                                               Oct 18, 2024    N/A                    pytest<9.0.0,>=8.3.3
+   :pypi:`pytest-youqu-playwright`                  pytest-youqu-playwright                                                                                                                                                                                                                                                                                                                                                                 Jun 12, 2024    N/A                    pytest
+   :pypi:`pytest-yuk`                               Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk.                                                                                                                                                                                                                                                                                                  Mar 26, 2021    N/A                    pytest>=5.0.0
+   :pypi:`pytest-zafira`                            A Zafira plugin for pytest                                                                                                                                                                                                                                                                                                                                                              Sep 18, 2019    5 - Production/Stable  pytest (==4.1.1)
+   :pypi:`pytest-zap`                               OWASP ZAP plugin for py.test.                                                                                                                                                                                                                                                                                                                                                           May 12, 2014    4 - Beta               N/A
+   :pypi:`pytest-zcc`                               eee                                                                                                                                                                                                                                                                                                                                                                                     Jun 02, 2024    N/A                    N/A
+   :pypi:`pytest-zebrunner`                         Pytest connector for Zebrunner reporting                                                                                                                                                                                                                                                                                                                                                Jul 04, 2024    5 - Production/Stable  pytest>=4.5.0
+   :pypi:`pytest-zeebe`                             Pytest fixtures for testing Camunda 8 processes using a Zeebe test engine.                                                                                                                                                                                                                                                                                                              Feb 01, 2024    N/A                    pytest (>=7.4.2,<8.0.0)
+   :pypi:`pytest-zephyr-scale-integration`          A library for integrating Jira Zephyr Scale (Adaptavist\TM4J) with pytest                                                                                                                                                                                                                                                                                                               Oct 15, 2024    N/A                    pytest
+   :pypi:`pytest-zephyr-telegram`                   Плагин для отправки данных автотестов в Телеграм и Зефир                                                                                                                                                                                                                                                                                                                                Sep 30, 2024    N/A                    pytest==8.3.2
+   :pypi:`pytest-zest`                              Zesty additions to pytest.                                                                                                                                                                                                                                                                                                                                                              Nov 17, 2022    N/A                    N/A
+   :pypi:`pytest-zhongwen-wendang`                  PyTest 中文文档                                                                                                                                                                                                                                                                                                                                                                         Mar 04, 2024    4 - Beta               N/A
+   :pypi:`pytest-zigzag`                            Extend py.test for RPC OpenStack testing.                                                                                                                                                                                                                                                                                                                                               Feb 27, 2019    4 - Beta               pytest (~=3.6)
+   :pypi:`pytest-zulip`                             Pytest report plugin for Zulip                                                                                                                                                                                                                                                                                                                                                          May 07, 2022    5 - Production/Stable  pytest
+   :pypi:`pytest-zy`                                接口自动化测试框架                                                                                                                                                                                                                                                                                                                                                                      Mar 24, 2024    N/A                    pytest~=7.2.0
+   :pypi:`tursu`                                    🎬 A pytest plugin that transpiles Gherkin feature files to Python using AST, enforcing typing for ease of use and debugging.                                                                                                                                                                                                                                                           Apr 05, 2025    4 - Beta               pytest>=8.3.5
+   ===============================================  ======================================================================================================================================================================================================================================================================================================================================================================================  ==============  =====================  ================================================
 
 .. only:: latex
 
 
+  :pypi:`databricks-labs-pytester`
+     *last release*: Feb 27, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.3
+
+     Python Testing for Databricks
+
+  :pypi:`logassert`
+     *last release*: Jan 29, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest; extra == "dev"
+
+     Simple but powerful assertion and verification of logged lines
+
+  :pypi:`logot`
+     *last release*: Mar 23, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9,>=7; extra == "pytest"
+
+     Test whether your code is logging correctly 🪵
+
+  :pypi:`nuts`
+     *last release*: Jul 19, 2024,
+     *status*: N/A,
+     *requires*: pytest<8,>=7
+
+     Network Unit Testing System
+
+  :pypi:`pytest-abq`
+     *last release*: Apr 07, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest integration for the ABQ universal test runner.
+
+  :pypi:`pytest-abstracts`
+     *last release*: May 25, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     A contextmanager pytest fixture for handling multiple mock abstracts
+
   :pypi:`pytest-accept`
-     *last release*: Nov 22, 2021,
+     *last release*: Dec 08, 2024,
      *status*: N/A,
-     *requires*: pytest (>=6,<7)
+     *requires*: pytest>=7
 
      A pytest-plugin for updating doctest outputs
 
   :pypi:`pytest-adaptavist`
-     *last release*: Nov 30, 2021,
+     *last release*: Oct 13, 2022,
      *status*: N/A,
      *requires*: pytest (>=5.4.0)
 
      pytest plugin for generating test execution results within Jira Test Management (tm4j)
 
+  :pypi:`pytest-adaptavist-fixed`
+     *last release*: Jan 17, 2025,
+     *status*: N/A,
+     *requires*: pytest>=5.4.0
+
+     pytest plugin for generating test execution results within Jira Test Management (tm4j)
+
   :pypi:`pytest-addons-test`
      *last release*: Aug 02, 2021,
      *status*: N/A,
@@ -1021,6 +1739,20 @@ This list contains 963 plugins.
 
      Pytest plugin for writing Azure Data Factory integration tests
 
+  :pypi:`pytest-ads-testplan`
+     *last release*: Sep 15, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Azure DevOps Test Case reporting for pytest tests
+
+  :pypi:`pytest-affected`
+     *last release*: Nov 06, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
   :pypi:`pytest-agent`
      *last release*: Nov 25, 2021,
      *status*: N/A,
@@ -1035,13 +1767,34 @@ This list contains 963 plugins.
 
      pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details.
 
+  :pypi:`pytest-ai`
+     *last release*: Jan 22, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     A Python package to generate regular, edge-case, and security HTTP tests.
+
+  :pypi:`pytest-ai1899`
+     *last release*: Mar 13, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     pytest plugin for connecting to ai1899 smart system stack
+
   :pypi:`pytest-aio`
-     *last release*: Oct 20, 2021,
-     *status*: 4 - Beta,
+     *last release*: Jul 31, 2024,
+     *status*: 5 - Production/Stable,
      *requires*: pytest
 
      Pytest plugin for testing async python code
 
+  :pypi:`pytest-aioboto3`
+     *last release*: Jan 17, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     Aioboto3 Pytest with Moto
+
   :pypi:`pytest-aiofiles`
      *last release*: May 14, 2017,
      *status*: 5 - Production/Stable,
@@ -1049,31 +1802,45 @@ This list contains 963 plugins.
 
      pytest fixtures for writing aiofiles tests with pyfakefs
 
-  :pypi:`pytest-aiohttp`
-     *last release*: Dec 05, 2017,
+  :pypi:`pytest-aiogram`
+     *last release*: May 06, 2023,
      *status*: N/A,
-     *requires*: pytest
+     *requires*: N/A
 
-     pytest plugin for aiohttp support
+
+
+  :pypi:`pytest-aiohttp`
+     *last release*: Jan 23, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.1.0
+
+     Pytest plugin for aiohttp support
 
   :pypi:`pytest-aiohttp-client`
-     *last release*: Nov 01, 2020,
+     *last release*: Jan 10, 2023,
      *status*: N/A,
-     *requires*: pytest (>=6)
+     *requires*: pytest (>=7.2.0,<8.0.0)
 
      Pytest \`client\` fixture for the Aiohttp
 
+  :pypi:`pytest-aiomoto`
+     *last release*: Jun 24, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.0,<8.0)
+
+     pytest-aiomoto
+
   :pypi:`pytest-aioresponses`
-     *last release*: Jul 29, 2021,
+     *last release*: Jan 02, 2025,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest>=3.5.0
 
      py.test integration for aioresponses
 
   :pypi:`pytest-aioworkers`
-     *last release*: Dec 04, 2019,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+     *last release*: Dec 26, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=8.3.4
 
      A plugin to test aioworkers project with pytest
 
@@ -1092,12 +1859,19 @@ This list contains 963 plugins.
 
 
   :pypi:`pytest-alembic`
-     *last release*: Dec 02, 2021,
+     *last release*: Jul 29, 2024,
      *status*: N/A,
-     *requires*: pytest (>=1.0)
+     *requires*: pytest>=6.0
 
      A pytest plugin for verifying alembic migrations.
 
+  :pypi:`pytest-alerts`
+     *last release*: Feb 21, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.4.0
+
+     A pytest plugin for sending test results to Slack and Telegram
+
   :pypi:`pytest-allclose`
      *last release*: Jul 30, 2019,
      *status*: 5 - Production/Stable,
@@ -1119,6 +1893,13 @@ This list contains 963 plugins.
 
      Plugin for py.test to generate allure xml reports
 
+  :pypi:`pytest-allure-collection`
+     *last release*: Apr 13, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     pytest plugin to collect allure markers without running any tests
+
   :pypi:`pytest-allure-dsl`
      *last release*: Oct 25, 2020,
      *status*: 4 - Beta,
@@ -1126,6 +1907,20 @@ This list contains 963 plugins.
 
      pytest plugin to test case doc string dls instructions
 
+  :pypi:`pytest-allure-id2history`
+     *last release*: May 14, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     Overwrite allure history id with testcase full name and testcase id if testcase has id, exclude parameters.
+
+  :pypi:`pytest-allure-intersection`
+     *last release*: Oct 27, 2022,
+     *status*: N/A,
+     *requires*: pytest (<5)
+
+
+
   :pypi:`pytest-allure-spec-coverage`
      *last release*: Oct 26, 2021,
      *status*: N/A,
@@ -1134,12 +1929,26 @@ This list contains 963 plugins.
      The pytest plugin aimed to display test coverage of the specs(requirements) in Allure
 
   :pypi:`pytest-alphamoon`
-     *last release*: Oct 21, 2021,
-     *status*: 4 - Beta,
+     *last release*: Dec 30, 2021,
+     *status*: 5 - Production/Stable,
      *requires*: pytest (>=3.5.0)
 
      Static code checks used at Alphamoon
 
+  :pypi:`pytest-amaranth-sim`
+     *last release*: Sep 21, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     Fixture to automate running Amaranth simulations
+
+  :pypi:`pytest-analyzer`
+     *last release*: Feb 21, 2024,
+     *status*: N/A,
+     *requires*: pytest <8.0.0,>=7.3.1
+
+     this plugin allows to analyze tests in pytest project, collect test metadata and sync it with testomat.io TCM system
+
   :pypi:`pytest-android`
      *last release*: Feb 21, 2019,
      *status*: 3 - Alpha,
@@ -1148,25 +1957,32 @@ This list contains 963 plugins.
      This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2.
 
   :pypi:`pytest-anki`
-     *last release*: Oct 14, 2021,
+     *last release*: Jul 31, 2022,
      *status*: 4 - Beta,
      *requires*: pytest (>=3.5.0)
 
      A pytest plugin for testing Anki add-ons
 
   :pypi:`pytest-annotate`
-     *last release*: Nov 29, 2021,
+     *last release*: Jun 07, 2022,
      *status*: 3 - Alpha,
-     *requires*: pytest (<7.0.0,>=3.2.0)
+     *requires*: pytest (<8.0.0,>=3.2.0)
 
      pytest-annotate: Generate PyAnnotate annotations from your pytest tests.
 
+  :pypi:`pytest-annotated`
+     *last release*: Sep 30, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8.3.3
+
+     Pytest plugin to allow use of Annotated in tests to resolve fixtures
+
   :pypi:`pytest-ansible`
-     *last release*: May 25, 2021,
+     *last release*: Apr 01, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest>=6
 
-     Plugin for py.test to simplify calling ansible modules from tests or fixtures
+     Plugin for pytest to simplify calling ansible modules from tests or fixtures
 
   :pypi:`pytest-ansible-playbook`
      *last release*: Mar 08, 2019,
@@ -1182,10 +1998,17 @@ This list contains 963 plugins.
 
      Pytest fixture which runs given ansible playbook file.
 
+  :pypi:`pytest-ansible-units`
+     *last release*: Apr 14, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     A pytest plugin for running unit tests within an ansible collection
+
   :pypi:`pytest-antilru`
-     *last release*: Apr 11, 2019,
+     *last release*: Jul 28, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest
+     *requires*: pytest>=7; python_version >= "3.10"
 
      Bust functools.lru_cache when running pytest to avoid test pollution
 
@@ -1197,25 +2020,39 @@ This list contains 963 plugins.
      The pytest anyio plugin is built into anyio. You don't need this package.
 
   :pypi:`pytest-anything`
-     *last release*: Feb 18, 2021,
+     *last release*: Jan 18, 2024,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest
 
      Pytest fixtures to assert anything and something
 
   :pypi:`pytest-aoc`
-     *last release*: Nov 23, 2021,
-     *status*: N/A,
+     *last release*: Dec 02, 2023,
+     *status*: 5 - Production/Stable,
      *requires*: pytest ; extra == 'test'
 
      Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures
 
+  :pypi:`pytest-aoreporter`
+     *last release*: Jun 27, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest report
+
   :pypi:`pytest-api`
-     *last release*: May 04, 2021,
+     *last release*: May 12, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.1.1,<8.0.0)
+
+     An ASGI middleware to populate OpenAPI Specification examples from pytest functions
+
+  :pypi:`pytest-api-soup`
+     *last release*: Aug 27, 2022,
      *status*: N/A,
      *requires*: N/A
 
-     PyTest-API Python Web Framework built for testing purposes.
+     Validate multiple endpoints with unit testing using a single source of truth.
 
   :pypi:`pytest-apistellar`
      *last release*: Jun 18, 2019,
@@ -1224,6 +2061,13 @@ This list contains 963 plugins.
 
      apistellar plugin for pytest.
 
+  :pypi:`pytest-apiver`
+     *last release*: Jun 21, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+
+
   :pypi:`pytest-appengine`
      *last release*: Feb 27, 2017,
      *status*: N/A,
@@ -1239,12 +2083,26 @@ This list contains 963 plugins.
      Pytest plugin for appium
 
   :pypi:`pytest-approvaltests`
-     *last release*: Feb 07, 2021,
+     *last release*: May 08, 2022,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest (>=7.0.1)
 
      A plugin to use approvaltests with pytest
 
+  :pypi:`pytest-approvaltests-geo`
+     *last release*: Feb 05, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Extension for ApprovalTests.Python specific to geo data verification
+
+  :pypi:`pytest-archon`
+     *last release*: Dec 18, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest >=7.2
+
+     Rule your architecture like a real developer
+
   :pypi:`pytest-argus`
      *last release*: Jun 24, 2021,
      *status*: 5 - Production/Stable,
@@ -1252,10 +2110,17 @@ This list contains 963 plugins.
 
      pyest results colection plugin
 
+  :pypi:`pytest-argus-server`
+     *last release*: Mar 24, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A plugin that provides a running Argus API server for tests
+
   :pypi:`pytest-arraydiff`
-     *last release*: Dec 06, 2018,
+     *last release*: Nov 27, 2023,
      *status*: 4 - Beta,
-     *requires*: pytest
+     *requires*: pytest >=4.6
 
      pytest plugin to help with comparing array output from tests
 
@@ -1266,6 +2131,13 @@ This list contains 963 plugins.
 
      Convenient ASGI client/server fixtures for Pytest
 
+  :pypi:`pytest-aspec`
+     *last release*: Dec 20, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     A rspec format reporter for pytest
+
   :pypi:`pytest-asptest`
      *last release*: Apr 28, 2018,
      *status*: 4 - Beta,
@@ -1273,6 +2145,20 @@ This list contains 963 plugins.
 
      test Answer Set Programming programs
 
+  :pypi:`pytest-assertcount`
+     *last release*: Oct 23, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=5.0.0)
+
+     Plugin to count actual number of asserts in pytest
+
+  :pypi:`pytest-assertions`
+     *last release*: Apr 27, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest Assertions
+
   :pypi:`pytest-assertutil`
      *last release*: May 10, 2019,
      *status*: N/A,
@@ -1281,12 +2167,19 @@ This list contains 963 plugins.
      pytest-assertutil
 
   :pypi:`pytest-assert-utils`
-     *last release*: Sep 21, 2021,
+     *last release*: Apr 14, 2022,
      *status*: 3 - Alpha,
      *requires*: N/A
 
      Useful assertion utilities for use with pytest
 
+  :pypi:`pytest-assist`
+     *last release*: Mar 17, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     load testing library
+
   :pypi:`pytest-assume`
      *last release*: Jun 24, 2021,
      *status*: N/A,
@@ -1294,6 +2187,13 @@ This list contains 963 plugins.
 
      A pytest plugin that allows multiple failures per test
 
+  :pypi:`pytest-assurka`
+     *last release*: Aug 04, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     A pytest plugin for Assurka Studio
+
   :pypi:`pytest-ast-back-to-python`
      *last release*: Sep 29, 2019,
      *status*: 4 - Beta,
@@ -1301,17 +2201,24 @@ This list contains 963 plugins.
 
      A plugin for pytest devs to view how assertion rewriting recodes the AST
 
+  :pypi:`pytest-asteroid`
+     *last release*: Aug 15, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=6.2.5,<8.0.0)
+
+     PyTest plugin for docker-based testing on database images
+
   :pypi:`pytest-astropy`
-     *last release*: Sep 21, 2021,
+     *last release*: Sep 26, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=4.6)
+     *requires*: pytest >=4.6
 
      Meta-package containing dependencies for testing
 
   :pypi:`pytest-astropy-header`
-     *last release*: Dec 18, 2019,
+     *last release*: Sep 06, 2022,
      *status*: 3 - Alpha,
-     *requires*: pytest (>=2.8)
+     *requires*: pytest (>=4.6)
 
      pytest plugin to add diagnostic information to the header of the test output
 
@@ -1322,16 +2229,37 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest_async`
+     *last release*: Feb 26, 2020,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest-async - Run your coroutine in event loop without decorator
+
+  :pypi:`pytest-async-generators`
+     *last release*: Jul 05, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest fixtures for async generators
+
   :pypi:`pytest-asyncio`
-     *last release*: Oct 15, 2021,
+     *last release*: Mar 25, 2025,
      *status*: 4 - Beta,
-     *requires*: pytest (>=5.4.0)
+     *requires*: pytest<9,>=8.2
 
-     Pytest support for asyncio.
+     Pytest support for asyncio
 
-  :pypi:`pytest-asyncio-cooperative`
-     *last release*: Oct 12, 2021,
+  :pypi:`pytest-asyncio-concurrent`
+     *last release*: Mar 16, 2025,
      *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     Pytest plugin to execute python async tests concurrently.
+
+  :pypi:`pytest-asyncio-cooperative`
+     *last release*: Jul 04, 2024,
+     *status*: N/A,
      *requires*: N/A
 
      Run all your asynchronous tests cooperatively.
@@ -1357,6 +2285,13 @@ This list contains 963 plugins.
 
      Database testing fixtures using the SQLAlchemy asyncio API
 
+  :pypi:`pytest-atf-allure`
+     *last release*: Nov 29, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.4.2,<8.0.0)
+
+     基于allure-pytest进行自定义
+
   :pypi:`pytest-atomic`
      *last release*: Nov 24, 2018,
      *status*: 4 - Beta,
@@ -1364,6 +2299,13 @@ This list contains 963 plugins.
 
      Skip rest of tests if previous test failed.
 
+  :pypi:`pytest-atstack`
+     *last release*: Jan 02, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A simple plugin to use with pytest
+
   :pypi:`pytest-attrib`
      *last release*: May 24, 2016,
      *status*: 4 - Beta,
@@ -1371,6 +2313,13 @@ This list contains 963 plugins.
 
      pytest plugin to select tests based on attributes similar to the nose-attrib plugin
 
+  :pypi:`pytest-attributes`
+     *last release*: Jun 24, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A plugin that allows users to add attributes to their tests. These attributes can then be referenced by fixtures or the test itself.
+
   :pypi:`pytest-austin`
      *last release*: Oct 11, 2020,
      *status*: 4 - Beta,
@@ -1378,6 +2327,13 @@ This list contains 963 plugins.
 
      Austin plugin for pytest
 
+  :pypi:`pytest-autocap`
+     *last release*: May 15, 2022,
+     *status*: N/A,
+     *requires*: pytest (<7.2,>=7.1.2)
+
+     automatically capture test & fixture stdout/stderr to files
+
   :pypi:`pytest-autochecklog`
      *last release*: Apr 25, 2015,
      *status*: 4 - Beta,
@@ -1385,15 +2341,22 @@ This list contains 963 plugins.
 
      automatically check condition and log all the checks
 
+  :pypi:`pytest-autofixture`
+     *last release*: Aug 01, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8
+
+     simplify pytest fixtures
+
   :pypi:`pytest-automation`
-     *last release*: Oct 01, 2021,
+     *last release*: Apr 24, 2024,
      *status*: N/A,
-     *requires*: pytest
+     *requires*: pytest>=7.0.0
 
      pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality.
 
   :pypi:`pytest-automock`
-     *last release*: Apr 22, 2020,
+     *last release*: May 16, 2023,
      *status*: N/A,
      *requires*: pytest ; extra == 'dev'
 
@@ -1413,6 +2376,13 @@ This list contains 963 plugins.
 
      This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2.
 
+  :pypi:`pytest-aviator`
+     *last release*: Nov 04, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Aviator's Flakybot pytest plugin that automatically reruns flaky tests.
+
   :pypi:`pytest-avoidance`
      *last release*: May 23, 2019,
      *status*: 4 - Beta,
@@ -1427,6 +2397,13 @@ This list contains 963 plugins.
 
      pytest plugin for testing AWS resource configurations
 
+  :pypi:`pytest-aws-apigateway`
+     *last release*: May 24, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     pytest plugin for AWS ApiGateway
+
   :pypi:`pytest-aws-config`
      *last release*: May 28, 2021,
      *status*: N/A,
@@ -1434,6 +2411,13 @@ This list contains 963 plugins.
 
      Protect your AWS credentials in unit tests
 
+  :pypi:`pytest-aws-fixtures`
+     *last release*: Apr 04, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.0.0
+
+     A series of fixtures to use in integration tests involving actual AWS services.
+
   :pypi:`pytest-axe`
      *last release*: Nov 12, 2018,
      *status*: N/A,
@@ -1441,11 +2425,32 @@ This list contains 963 plugins.
 
      pytest plugin for axe-selenium-python
 
-  :pypi:`pytest-azurepipelines`
-     *last release*: Jul 23, 2020,
+  :pypi:`pytest-axe-playwright-snapshot`
+     *last release*: Jul 25, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     A pytest plugin that runs Axe-core on Playwright pages and takes snapshots of the results.
+
+  :pypi:`pytest-azure`
+     *last release*: Jan 18, 2023,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     Pytest utilities and mocks for Azure
+
+  :pypi:`pytest-azure-devops`
+     *last release*: Jun 20, 2022,
      *status*: 4 - Beta,
      *requires*: pytest (>=3.5.0)
 
+     Simplifies using azure devops parallel strategy (https://docs.microsoft.com/en-us/azure/devops/pipelines/test/parallel-testing-any-test-runner) with pytest.
+
+  :pypi:`pytest-azurepipelines`
+     *last release*: Oct 06, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=5.0.0)
+
      Formatting PyTest output for Azure Pipelines UI
 
   :pypi:`pytest-bandit`
@@ -1455,20 +2460,62 @@ This list contains 963 plugins.
 
      A bandit plugin for pytest
 
+  :pypi:`pytest-bandit-xayon`
+     *last release*: Oct 17, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     A bandit plugin for pytest
+
   :pypi:`pytest-base-url`
-     *last release*: Jun 19, 2020,
+     *last release*: Jan 31, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=2.7.3)
+     *requires*: pytest>=7.0.0
 
      pytest plugin for URL based testing
 
+  :pypi:`pytest-batch-regression`
+     *last release*: May 08, 2024,
+     *status*: N/A,
+     *requires*: pytest>=6.0.0
+
+     A pytest plugin to repeat the entire test suite in batches.
+
+  :pypi:`pytest-bazel`
+     *last release*: Sep 27, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     A pytest runner with bazel support
+
   :pypi:`pytest-bdd`
-     *last release*: Oct 25, 2021,
+     *last release*: Dec 05, 2024,
      *status*: 6 - Mature,
-     *requires*: pytest (>=4.3)
+     *requires*: pytest>=7.0.0
+
+     BDD for pytest
+
+  :pypi:`pytest-bdd-html`
+     *last release*: Nov 22, 2022,
+     *status*: 3 - Alpha,
+     *requires*: pytest (!=6.0.0,>=5.0)
+
+     pytest plugin to display BDD info in HTML test report
+
+  :pypi:`pytest-bdd-ng`
+     *last release*: Nov 26, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=5.2
 
      BDD for pytest
 
+  :pypi:`pytest-bdd-report`
+     *last release*: Nov 27, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.1.3
+
+     A pytest-bdd plugin for generating useful and informative BDD test reports
+
   :pypi:`pytest-bdd-splinter`
      *last release*: Aug 12, 2019,
      *status*: 5 - Production/Stable,
@@ -1497,6 +2544,20 @@ This list contains 963 plugins.
 
      A pytest plugin that reports test results to the BeakerLib framework
 
+  :pypi:`pytest-beartype`
+     *last release*: Oct 31, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest plugin to run your tests with beartype checking enabled.
+
+  :pypi:`pytest-bec-e2e`
+     *last release*: Apr 01, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     BEC pytest plugin for end-to-end tests
+
   :pypi:`pytest-beds`
      *last release*: Jun 07, 2016,
      *status*: 4 - Beta,
@@ -1504,6 +2565,13 @@ This list contains 963 plugins.
 
      Fixtures for testing Google Appengine (GAE) apps
 
+  :pypi:`pytest-beeprint`
+     *last release*: Jul 04, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     use icdiff for better error messages in pytest assertions
+
   :pypi:`pytest-bench`
      *last release*: Jul 21, 2014,
      *status*: 3 - Alpha,
@@ -1512,37 +2580,58 @@ This list contains 963 plugins.
      Benchmark utility that plugs into pytest.
 
   :pypi:`pytest-benchmark`
-     *last release*: Apr 17, 2021,
+     *last release*: Oct 30, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.8)
+     *requires*: pytest>=8.1
 
      A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer.
 
+  :pypi:`pytest-better-datadir`
+     *last release*: Mar 13, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     A small example package
+
+  :pypi:`pytest-better-parametrize`
+     *last release*: Mar 05, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest >=6.2.0
+
+     Better description of parametrized test cases
+
   :pypi:`pytest-bg-process`
-     *last release*: Aug 17, 2021,
+     *last release*: Jan 24, 2022,
      *status*: 4 - Beta,
      *requires*: pytest (>=3.5.0)
 
      Pytest plugin to initialize background process
 
   :pypi:`pytest-bigchaindb`
-     *last release*: Aug 17, 2021,
+     *last release*: Jan 24, 2022,
      *status*: 4 - Beta,
      *requires*: N/A
 
      A BigchainDB plugin for pytest.
 
   :pypi:`pytest-bigquery-mock`
-     *last release*: Aug 05, 2021,
+     *last release*: Dec 28, 2022,
      *status*: N/A,
      *requires*: pytest (>=5.0)
 
      Provides a mock fixture for python bigquery client
 
+  :pypi:`pytest-bisect-tests`
+     *last release*: Jun 09, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Find tests leaking state and affecting other
+
   :pypi:`pytest-black`
-     *last release*: Oct 05, 2020,
+     *last release*: Dec 15, 2024,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest>=7.0.0
 
      A pytest plugin to enable format checking with black
 
@@ -1553,6 +2642,13 @@ This list contains 963 plugins.
 
      Allow '--black' on older Pythons
 
+  :pypi:`pytest-black-ng`
+     *last release*: Oct 20, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=7.0.0)
+
+     A pytest plugin to enable format checking with black
+
   :pypi:`pytest-blame`
      *last release*: May 04, 2019,
      *status*: N/A,
@@ -1561,9 +2657,9 @@ This list contains 963 plugins.
      A pytest plugin helps developers to debug by providing useful commits history.
 
   :pypi:`pytest-blender`
-     *last release*: Oct 29, 2021,
+     *last release*: Aug 02, 2024,
      *status*: N/A,
-     *requires*: pytest (==6.2.5) ; extra == 'dev'
+     *requires*: pytest
 
      Blender Pytest plugin.
 
@@ -1575,7 +2671,7 @@ This list contains 963 plugins.
      Pytest plugin to emit notifications via the Blink(1) RGB LED
 
   :pypi:`pytest-blockage`
-     *last release*: Feb 13, 2019,
+     *last release*: Dec 21, 2021,
      *status*: N/A,
      *requires*: pytest
 
@@ -1588,6 +2684,13 @@ This list contains 963 plugins.
 
      pytest plugin to mark a test as blocker and skip all other tests
 
+  :pypi:`pytest-blue`
+     *last release*: Sep 05, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     A pytest plugin that adds a \`blue\` fixture for printing stuff in blue.
+
   :pypi:`pytest-board`
      *last release*: Jan 20, 2019,
      *status*: N/A,
@@ -1595,6 +2698,34 @@ This list contains 963 plugins.
 
      Local continuous test runner with pytest and watchdog.
 
+  :pypi:`pytest-boilerplate`
+     *last release*: Sep 12, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=4.0.0
+
+     The pytest plugin for your Django Boilerplate.
+
+  :pypi:`pytest-boost-xml`
+     *last release*: Nov 30, 2022,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Plugin for pytest to generate boost xml reports
+
+  :pypi:`pytest-bootstrap`
+     *last release*: Mar 04, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
+  :pypi:`pytest-boto-mock`
+     *last release*: Jul 16, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=8.2.0
+
+     Thin-wrapper around the mock package for easier use with pytest
+
   :pypi:`pytest-bpdb`
      *last release*: Jan 19, 2015,
      *status*: 2 - Pre-Alpha,
@@ -1602,8 +2733,15 @@ This list contains 963 plugins.
 
      A py.test plug-in to enable drop to bpdb debugger on test failure.
 
+  :pypi:`pytest-bq`
+     *last release*: May 08, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=6.2
+
+     BigQuery fixtures and fixture factories for Pytest.
+
   :pypi:`pytest-bravado`
-     *last release*: Jul 19, 2021,
+     *last release*: Feb 15, 2022,
      *status*: N/A,
      *requires*: N/A
 
@@ -1630,6 +2768,13 @@ This list contains 963 plugins.
 
      A pytest plugin for running tests on a Briefcase project.
 
+  :pypi:`pytest-broadcaster`
+     *last release*: Mar 02, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     Pytest plugin to broadcast pytest output to various destinations
+
   :pypi:`pytest-browser`
      *last release*: Dec 10, 2016,
      *status*: 3 - Alpha,
@@ -1644,6 +2789,13 @@ This list contains 963 plugins.
 
      BrowserMob proxy plugin for py.test.
 
+  :pypi:`pytest_browserstack`
+     *last release*: Jan 27, 2016,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Py.test plugin for BrowserStack
+
   :pypi:`pytest-browserstack-local`
      *last release*: Feb 09, 2018,
      *status*: N/A,
@@ -1651,15 +2803,22 @@ This list contains 963 plugins.
 
      \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background.
 
+  :pypi:`pytest-budosystems`
+     *last release*: May 07, 2023,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     Budo Systems is a martial arts school management system. This module is the Budo Systems Pytest Plugin.
+
   :pypi:`pytest-bug`
-     *last release*: Jun 02, 2020,
+     *last release*: Jun 05, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.6.0)
+     *requires*: pytest>=8.0.0
 
      Pytest plugin for marking tests as a bug
 
   :pypi:`pytest-bugtong-tag`
-     *last release*: Apr 23, 2021,
+     *last release*: Jan 16, 2022,
      *status*: N/A,
      *requires*: N/A
 
@@ -1694,7 +2853,7 @@ This list contains 963 plugins.
 
 
   :pypi:`pytest-bwrap`
-     *last release*: Oct 26, 2018,
+     *last release*: Feb 25, 2024,
      *status*: 3 - Alpha,
      *requires*: N/A
 
@@ -1708,9 +2867,9 @@ This list contains 963 plugins.
      pytest plugin with mechanisms for caching across test runs
 
   :pypi:`pytest-cache-assert`
-     *last release*: Nov 03, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=5)
+     *last release*: Aug 14, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=6.0.0)
 
      Cache assertion data to simplify regression testing of complex serializable data
 
@@ -1721,6 +2880,20 @@ This list contains 963 plugins.
 
      Pytest plugin to only run tests affected by changes
 
+  :pypi:`pytest-cairo`
+     *last release*: Apr 17, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest support for cairo-lang and starknet
+
+  :pypi:`pytest-call-checker`
+     *last release*: Oct 16, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=7.1.3,<8.0.0)
+
+     Small pytest utility to easily create test doubles
+
   :pypi:`pytest-camel-collect`
      *last release*: Aug 02, 2020,
      *status*: N/A,
@@ -1742,6 +2915,13 @@ This list contains 963 plugins.
 
      A plugin that replays pRNG state on failure.
 
+  :pypi:`pytest-capsqlalchemy`
+     *last release*: Mar 19, 2025,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Pytest plugin to allow capturing SQLAlchemy queries.
+
   :pypi:`pytest-capture-deprecatedwarnings`
      *last release*: Apr 30, 2019,
      *status*: N/A,
@@ -1749,15 +2929,22 @@ This list contains 963 plugins.
 
      pytest plugin to capture all deprecatedwarnings and put them in one file
 
-  :pypi:`pytest-capturelogs`
-     *last release*: Sep 11, 2021,
-     *status*: 3 - Alpha,
-     *requires*: N/A
+  :pypi:`pytest-capture-warnings`
+     *last release*: May 03, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     pytest plugin to capture all warnings and put them in one file of your choice
+
+  :pypi:`pytest-case`
+     *last release*: Nov 25, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.3.3
 
-     A sample Python project
+     A clean, modern, wrapper for pytest.mark.parametrize
 
   :pypi:`pytest-cases`
-     *last release*: Nov 08, 2021,
+     *last release*: Sep 26, 2024,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
@@ -1784,12 +2971,26 @@ This list contains 963 plugins.
 
      Pytest plugin with server for catching HTTP requests.
 
+  :pypi:`pytest-cdist`
+     *last release*: Jan 30, 2025,
+     *status*: N/A,
+     *requires*: pytest>=7
+
+     A pytest plugin to split your test suite into multiple parts
+
   :pypi:`pytest-celery`
-     *last release*: May 06, 2021,
+     *last release*: Feb 21, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Pytest plugin for Celery
+
+  :pypi:`pytest-cfg-fetcher`
+     *last release*: Feb 26, 2024,
      *status*: N/A,
      *requires*: N/A
 
-     pytest-celery a shim pytest plugin to enable celery.contrib.pytest
+     Pass config options to your unit tests.
 
   :pypi:`pytest-chainmaker`
      *last release*: Oct 15, 2021,
@@ -1805,6 +3006,20 @@ This list contains 963 plugins.
 
      A set of py.test fixtures for AWS Chalice
 
+  :pypi:`pytest-change-assert`
+     *last release*: Oct 19, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     修改报错中文为英文
+
+  :pypi:`pytest-change-demo`
+     *last release*: Mar 02, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     turn . into √,turn F into x
+
   :pypi:`pytest-change-report`
      *last release*: Sep 14, 2020,
      *status*: N/A,
@@ -1812,6 +3027,13 @@ This list contains 963 plugins.
 
      turn . into √,turn F into x
 
+  :pypi:`pytest-change-xds`
+     *last release*: Apr 16, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     turn . into √,turn F into x
+
   :pypi:`pytest-chdir`
      *last release*: Jan 28, 2020,
      *status*: N/A,
@@ -1819,27 +3041,55 @@ This list contains 963 plugins.
 
      A pytest fixture for changing current working directory
 
+  :pypi:`pytest-check`
+     *last release*: Apr 04, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.0.0
+
+     A pytest plugin that allows multiple failures per test.
+
   :pypi:`pytest-checkdocs`
-     *last release*: Jul 31, 2021,
+     *last release*: Apr 30, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=4.6) ; extra == 'testing'
+     *requires*: pytest!=8.1.*,>=6; extra == "testing"
 
      check the README when running tests
 
   :pypi:`pytest-checkipdb`
-     *last release*: Jul 22, 2020,
+     *last release*: Dec 04, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=2.9.2)
+     *requires*: pytest >=2.9.2
 
      plugin to check if there are ipdb debugs left
 
+  :pypi:`pytest-check-library`
+     *last release*: Jul 17, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     check your missing library
+
+  :pypi:`pytest-check-libs`
+     *last release*: Jul 17, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     check your missing library
+
   :pypi:`pytest-check-links`
      *last release*: Jul 29, 2020,
      *status*: N/A,
-     *requires*: pytest (>=4.6)
+     *requires*: pytest<9,>=7.0
 
      Check links in files
 
+  :pypi:`pytest-checklist`
+     *last release*: Jun 10, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest plugin to track and report unit/function coverage.
+
   :pypi:`pytest-check-mk`
      *last release*: Nov 19, 2015,
      *status*: 4 - Beta,
@@ -1847,6 +3097,62 @@ This list contains 963 plugins.
 
      pytest plugin to test Check_MK checks
 
+  :pypi:`pytest-checkpoint`
+     *last release*: Mar 30, 2025,
+     *status*: N/A,
+     *requires*: pytest>=8.0.0
+
+     Restore a checkpoint in pytest
+
+  :pypi:`pytest-check-requirements`
+     *last release*: Feb 20, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     A package to prevent Dependency Confusion attacks against Yandex.
+
+  :pypi:`pytest-ch-framework`
+     *last release*: Apr 17, 2024,
+     *status*: N/A,
+     *requires*: pytest==8.0.1
+
+     My pytest framework
+
+  :pypi:`pytest-chic-report`
+     *last release*: Nov 01, 2024,
+     *status*: N/A,
+     *requires*: pytest>=6.0
+
+     Simple pytest plugin for generating and sending report to messengers.
+
+  :pypi:`pytest-chinesereport`
+     *last release*: Mar 19, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=3.5.0
+
+
+
+  :pypi:`pytest-choose`
+     *last release*: Feb 04, 2024,
+     *status*: N/A,
+     *requires*: pytest >=7.0.0
+
+     Provide the pytest with the ability to collect use cases based on rules in text files
+
+  :pypi:`pytest-chunks`
+     *last release*: Jul 05, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=6.0.0)
+
+     Run only a chunk of your test suite
+
+  :pypi:`pytest_cid`
+     *last release*: Sep 01, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest >= 5.0, < 7.0
+
+     Compare data structures containing matching CIDs of different versions and encoding
+
   :pypi:`pytest-circleci`
      *last release*: May 03, 2019,
      *status*: N/A,
@@ -1855,12 +3161,19 @@ This list contains 963 plugins.
      py.test plugin for CircleCI
 
   :pypi:`pytest-circleci-parallelized`
-     *last release*: Mar 26, 2019,
+     *last release*: Oct 20, 2022,
      *status*: N/A,
      *requires*: N/A
 
      Parallelize pytest across CircleCI workers.
 
+  :pypi:`pytest-circleci-parallelized-rjp`
+     *last release*: Jun 21, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     Parallelize pytest across CircleCI workers.
+
   :pypi:`pytest-ckan`
      *last release*: Apr 28, 2020,
      *status*: 4 - Beta,
@@ -1875,24 +3188,80 @@ This list contains 963 plugins.
 
      A plugin providing an alternative, colourful diff output for failing assertions.
 
+  :pypi:`pytest-class-fixtures`
+     *last release*: Nov 15, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.3.3
+
+     Class as PyTest fixtures (and BDD steps)
+
   :pypi:`pytest-cldf`
-     *last release*: May 06, 2019,
+     *last release*: Nov 07, 2022,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest (>=3.6)
 
      Easy quality control for CLDF datasets using pytest
 
+  :pypi:`pytest-clean-database`
+     *last release*: Mar 14, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest<9,>=7.0
+
+     A pytest plugin that cleans your database up after every test.
+
+  :pypi:`pytest-cleanslate`
+     *last release*: Sep 04, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Collects and executes pytest tests separately
+
+  :pypi:`pytest_cleanup`
+     *last release*: Jan 28, 2020,
+     *status*: N/A,
+     *requires*: N/A
+
+     Automated, comprehensive and well-organised pytest test cases.
+
+  :pypi:`pytest-cleanuptotal`
+     *last release*: Nov 08, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     A cleanup plugin for pytest
+
+  :pypi:`pytest-clerk`
+     *last release*: Jan 30, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.0.0
+
+     A set of pytest fixtures to help with integration testing with Clerk.
+
+  :pypi:`pytest-cli2-ansible`
+     *last release*: Mar 05, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
   :pypi:`pytest-click`
-     *last release*: Aug 29, 2020,
+     *last release*: Feb 11, 2022,
      *status*: 5 - Production/Stable,
      *requires*: pytest (>=5.0)
 
-     Py.test plugin for Click
+     Pytest plugin for Click
+
+  :pypi:`pytest-cli-fixtures`
+     *last release*: Jul 28, 2022,
+     *status*: N/A,
+     *requires*: pytest (~=7.0)
+
+     Automatically register fixtures for custom CLI arguments
 
   :pypi:`pytest-clld`
-     *last release*: Nov 29, 2021,
+     *last release*: Oct 23, 2024,
      *status*: N/A,
-     *requires*: pytest (>=3.6)
+     *requires*: pytest>=3.9
 
 
 
@@ -1910,6 +3279,34 @@ This list contains 963 plugins.
 
      pytest plugin for testing cloudflare workers
 
+  :pypi:`pytest-cloudist`
+     *last release*: Sep 02, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=7.1.2,<8.0.0)
+
+     Distribute tests to cloud machines without fuss
+
+  :pypi:`pytest-cmake`
+     *last release*: Feb 17, 2025,
+     *status*: N/A,
+     *requires*: pytest<9,>=4
+
+     Provide CMake module for Pytest
+
+  :pypi:`pytest-cmake-presets`
+     *last release*: Dec 26, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.2.0,<8.0.0)
+
+     Execute CMake Presets via pytest
+
+  :pypi:`pytest-cmdline-add-args`
+     *last release*: Sep 01, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest plugin for custom argument handling and Allure reporting. This plugin allows you to add arguments before running a test.
+
   :pypi:`pytest-cobra`
      *last release*: Jun 29, 2019,
      *status*: 3 - Alpha,
@@ -1917,13 +3314,27 @@ This list contains 963 plugins.
 
      PyTest plugin for testing Smart Contracts for Ethereum blockchain.
 
-  :pypi:`pytest-codeblocks`
-     *last release*: Oct 13, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=6)
+  :pypi:`pytest-cocotb`
+     *last release*: Mar 15, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest; extra == "test"
+
+     Pytest plugin to integrate Cocotb
+
+  :pypi:`pytest_codeblocks`
+     *last release*: Sep 17, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest >= 7.0.0
 
      Test code blocks in your READMEs
 
+  :pypi:`pytest-codecarbon`
+     *last release*: Jun 15, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest plugin for measuring carbon emissions
+
   :pypi:`pytest-codecheckers`
      *last release*: Feb 13, 2010,
      *status*: N/A,
@@ -1932,9 +3343,9 @@ This list contains 963 plugins.
      pytest plugin to add source code sanity checks (pep8 and friends)
 
   :pypi:`pytest-codecov`
-     *last release*: Oct 27, 2021,
+     *last release*: Mar 25, 2025,
      *status*: 4 - Beta,
-     *requires*: pytest (>=4.6.0)
+     *requires*: pytest>=4.6.0
 
      Pytest plugin for uploading pytest-cov results to codecov.io
 
@@ -1945,6 +3356,13 @@ This list contains 963 plugins.
 
      Automatically create pytest test signatures
 
+  :pypi:`pytest-codeowners`
+     *last release*: Mar 30, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=6.0.0)
+
+     Pytest plugin for selecting tests by GitHub CODEOWNERS.
+
   :pypi:`pytest-codestyle`
      *last release*: Mar 23, 2020,
      *status*: 3 - Alpha,
@@ -1952,6 +3370,20 @@ This list contains 963 plugins.
 
      pytest plugin to run pycodestyle
 
+  :pypi:`pytest-codspeed`
+     *last release*: Jan 31, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=3.8
+
+     Pytest plugin to create CodSpeed benchmarks
+
+  :pypi:`pytest-collect-appoint-info`
+     *last release*: Aug 03, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     set your encoding
+
   :pypi:`pytest-collect-formatter`
      *last release*: Mar 29, 2021,
      *status*: 5 - Production/Stable,
@@ -1966,6 +3398,27 @@ This list contains 963 plugins.
 
      Formatter for pytest collect output
 
+  :pypi:`pytest-collect-interface-info-plugin`
+     *last release*: Sep 25, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Get executed interface information in pytest interface automation framework
+
+  :pypi:`pytest-collector`
+     *last release*: Aug 02, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.0,<8.0)
+
+     Python package for collecting pytest.
+
+  :pypi:`pytest-collect-pytest-interinfo`
+     *last release*: Sep 26, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     A simple plugin to use with pytest
+
   :pypi:`pytest-colordots`
      *last release*: Oct 06, 2017,
      *status*: 5 - Production/Stable,
@@ -1981,12 +3434,19 @@ This list contains 963 plugins.
      An interactive GUI test runner for PyTest
 
   :pypi:`pytest-common-subject`
-     *last release*: Nov 12, 2020,
+     *last release*: Jun 12, 2024,
      *status*: N/A,
-     *requires*: pytest (>=3.6,<7)
+     *requires*: pytest<9,>=3.6
 
      pytest framework for testing different aspects of a common method
 
+  :pypi:`pytest-compare`
+     *last release*: Jun 22, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     pytest plugin for comparing call arguments.
+
   :pypi:`pytest-concurrent`
      *last release*: Jan 12, 2019,
      *status*: 4 - Beta,
@@ -2002,16 +3462,16 @@ This list contains 963 plugins.
      Base configurations and utilities for developing    your Python project test suite with pytest.
 
   :pypi:`pytest-confluence-report`
-     *last release*: Nov 06, 2020,
+     *last release*: Apr 17, 2022,
      *status*: N/A,
      *requires*: N/A
 
      Package stands for pytest plugin to upload results into Confluence page.
 
   :pypi:`pytest-console-scripts`
-     *last release*: Sep 28, 2021,
+     *last release*: May 31, 2023,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest (>=4.0.0)
 
      Pytest plugin for testing console scripts
 
@@ -2023,9 +3483,9 @@ This list contains 963 plugins.
      pytest plugin with fixtures for testing consul aware apps
 
   :pypi:`pytest-container`
-     *last release*: Nov 19, 2021,
-     *status*: 3 - Alpha,
-     *requires*: pytest (>=3.10)
+     *last release*: Dec 04, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=3.10
 
      Pytest fixtures for writing container based tests
 
@@ -2043,16 +3503,37 @@ This list contains 963 plugins.
 
      A plugin to run tests written with the Contexts framework using pytest
 
+  :pypi:`pytest-continuous`
+     *last release*: Apr 23, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     A pytest plugin to run tests continuously until failure or interruption.
+
   :pypi:`pytest-cookies`
-     *last release*: May 24, 2021,
+     *last release*: Mar 22, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.3.0)
+     *requires*: pytest (>=3.9.0)
 
      The pytest plugin for your Cookiecutter templates. 🍪
 
-  :pypi:`pytest-couchdbkit`
-     *last release*: Apr 17, 2012,
-     *status*: N/A,
+  :pypi:`pytest-copie`
+     *last release*: Jan 31, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     The pytest plugin for your copier templates 📒
+
+  :pypi:`pytest-copier`
+     *last release*: Dec 11, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.3.2
+
+     A pytest plugin to help testing Copier templates
+
+  :pypi:`pytest-couchdbkit`
+     *last release*: Apr 17, 2012,
+     *status*: N/A,
      *requires*: N/A
 
      py.test extension for per-test couchdb databases using couchdbkit
@@ -2065,9 +3546,9 @@ This list contains 963 plugins.
      count erros and send email
 
   :pypi:`pytest-cov`
-     *last release*: Oct 04, 2021,
+     *last release*: Apr 05, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=4.6)
+     *requires*: pytest>=4.6
 
      Pytest plugin for measuring coverage.
 
@@ -2086,12 +3567,19 @@ This list contains 963 plugins.
 
 
   :pypi:`pytest-coverage-context`
-     *last release*: Jan 04, 2021,
+     *last release*: Jun 28, 2023,
      *status*: 4 - Beta,
-     *requires*: pytest (>=6.1.0)
+     *requires*: N/A
 
      Coverage dynamic context support for PyTest, including sub-processes
 
+  :pypi:`pytest-coveragemarkers`
+     *last release*: Oct 15, 2024,
+     *status*: N/A,
+     *requires*: pytest<8.0.0,>=7.1.2
+
+     Using pytest markers to track functional coverage and filtering of tests
+
   :pypi:`pytest-cov-exclude`
      *last release*: Apr 29, 2016,
      *status*: 4 - Beta,
@@ -2099,13 +3587,27 @@ This list contains 963 plugins.
 
      Pytest plugin for excluding tests based on coverage data
 
+  :pypi:`pytest_covid`
+     *last release*: Jun 24, 2020,
+     *status*: N/A,
+     *requires*: N/A
+
+     Too many faillure, less tests.
+
   :pypi:`pytest-cpp`
-     *last release*: Dec 03, 2021,
+     *last release*: Sep 18, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (!=5.4.0,!=5.4.1)
+     *requires*: pytest
 
      Use pytest's runner to discover and execute C++ tests
 
+  :pypi:`pytest-cqase`
+     *last release*: Aug 22, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.1.2,<8.0.0)
+
+     Custom qase pytest plugin
+
   :pypi:`pytest-cram`
      *last release*: Aug 08, 2020,
      *status*: N/A,
@@ -2120,6 +3622,34 @@ This list contains 963 plugins.
 
      Manages CrateDB instances during your integration tests
 
+  :pypi:`pytest-cratedb`
+     *last release*: Oct 08, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest<9
+
+     Manage CrateDB instances for integration tests
+
+  :pypi:`pytest-cratedb-reporter`
+     *last release*: Mar 11, 2025,
+     *status*: N/A,
+     *requires*: pytest>=6.0.0
+
+     A pytest plugin for reporting test results to CrateDB
+
+  :pypi:`pytest-crayons`
+     *last release*: Oct 08, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     A pytest plugin for colorful print statements
+
+  :pypi:`pytest-create`
+     *last release*: Feb 15, 2023,
+     *status*: 1 - Planning,
+     *requires*: N/A
+
+     pytest-create
+
   :pypi:`pytest-cricri`
      *last release*: Jan 27, 2018,
      *status*: N/A,
@@ -2141,10 +3671,17 @@ This list contains 963 plugins.
 
      CSV output for pytest.
 
+  :pypi:`pytest-csv-params`
+     *last release*: Oct 25, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9.0.0,>=8.3.0
+
+     Pytest plugin for Test Case Parametrization with CSV files
+
   :pypi:`pytest-curio`
-     *last release*: Oct 07, 2020,
+     *last release*: Oct 06, 2024,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest
 
      Pytest support for curio.
 
@@ -2176,6 +3713,13 @@ This list contains 963 plugins.
 
      Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report
 
+  :pypi:`pytest-custom-outputs`
+     *last release*: Jul 10, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures.
+
   :pypi:`pytest-custom-report`
      *last release*: Jan 30, 2019,
      *status*: N/A,
@@ -2190,17 +3734,31 @@ This list contains 963 plugins.
 
      Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report
 
-  :pypi:`pytest-cython`
-     *last release*: Jan 26, 2021,
+  :pypi:`pytest-custom-timeout`
+     *last release*: Jan 08, 2025,
      *status*: 4 - Beta,
-     *requires*: pytest (>=2.7.3)
+     *requires*: pytest>=8.0.0
+
+     Use custom logic when a test times out. Based on pytest-timeout.
+
+  :pypi:`pytest-cython`
+     *last release*: Apr 05, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=8
 
      A plugin for testing Cython extension modules
 
+  :pypi:`pytest-cython-collect`
+     *last release*: Jun 17, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+
+
   :pypi:`pytest-darker`
-     *last release*: Aug 16, 2020,
+     *last release*: Feb 25, 2024,
      *status*: N/A,
-     *requires*: pytest (>=6.0.1) ; extra == 'test'
+     *requires*: pytest <7,>=6.0.1
 
      A pytest plugin for checking of modified code using Darker
 
@@ -2211,6 +3769,13 @@ This list contains 963 plugins.
 
      pytest fixtures to run dash applications.
 
+  :pypi:`pytest-dashboard`
+     *last release*: May 30, 2024,
+     *status*: N/A,
+     *requires*: pytest<8.0.0,>=7.4.3
+
+
+
   :pypi:`pytest-data`
      *last release*: Nov 01, 2016,
      *status*: 5 - Production/Stable,
@@ -2218,6 +3783,13 @@ This list contains 963 plugins.
 
      Useful functions for managing data for pytest fixtures
 
+  :pypi:`pytest-databases`
+     *last release*: Mar 23, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Reusable database fixtures for any and all databases.
+
   :pypi:`pytest-databricks`
      *last release*: Jul 29, 2020,
      *status*: N/A,
@@ -2226,18 +3798,18 @@ This list contains 963 plugins.
      Pytest plugin for remote Databricks notebooks testing
 
   :pypi:`pytest-datadir`
-     *last release*: Oct 22, 2019,
+     *last release*: Feb 07, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=2.7.0)
+     *requires*: pytest>=7.0
 
      pytest plugin for test data directories and files
 
   :pypi:`pytest-datadir-mgr`
-     *last release*: Aug 16, 2021,
+     *last release*: Apr 06, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest
+     *requires*: pytest (>=7.1)
 
-     Manager for test data providing downloads, caching of generated files, and a context for temp directories.
+     Manager for test data: downloads, artifact caching, and a tmpdir context.
 
   :pypi:`pytest-datadir-ng`
      *last release*: Dec 25, 2019,
@@ -2246,6 +3818,20 @@ This list contains 963 plugins.
 
      Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem.
 
+  :pypi:`pytest-datadir-nng`
+     *last release*: Nov 09, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=7.0.0,<8.0.0)
+
+     Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem.
+
+  :pypi:`pytest-data-extractor`
+     *last release*: Jul 19, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.0.1)
+
+     A pytest plugin to extract relevant metadata about tests into an external file (currently only json support)
+
   :pypi:`pytest-data-file`
      *last release*: Dec 04, 2019,
      *status*: N/A,
@@ -2254,11 +3840,11 @@ This list contains 963 plugins.
      Fixture "data" and "case_data" for test from yaml file
 
   :pypi:`pytest-datafiles`
-     *last release*: Oct 07, 2018,
+     *last release*: Feb 24, 2023,
      *status*: 5 - Production/Stable,
      *requires*: pytest (>=3.6)
 
-     py.test plugin to create a 'tmpdir' containing predefined files/directories.
+     py.test plugin to create a 'tmp_path' containing predefined files/directories.
 
   :pypi:`pytest-datafixtures`
      *last release*: Dec 05, 2020,
@@ -2282,12 +3868,26 @@ This list contains 963 plugins.
      A pytest plugin for managing an archive of test data.
 
   :pypi:`pytest-datarecorder`
-     *last release*: Apr 20, 2020,
+     *last release*: Jul 31, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      A py.test plugin recording and comparing test output.
 
+  :pypi:`pytest-dataset`
+     *last release*: Sep 01, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Plugin for loading different datasets for pytest by prefix from json or yaml files
+
+  :pypi:`pytest-data-suites`
+     *last release*: Apr 06, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0,>=6.0
+
+     Class-based pytest parametrization
+
   :pypi:`pytest-datatest`
      *last release*: Oct 15, 2020,
      *status*: 4 - Beta,
@@ -2296,9 +3896,9 @@ This list contains 963 plugins.
      A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration).
 
   :pypi:`pytest-db`
-     *last release*: Dec 04, 2019,
+     *last release*: Aug 22, 2024,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest
 
      Session scope fixture "db" for mysql query or change
 
@@ -2316,6 +3916,13 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-dbt`
+     *last release*: Jun 08, 2023,
+     *status*: 2 - Pre-Alpha,
+     *requires*: pytest (>=7.0.0,<8.0.0)
+
+     Unit test dbt models with standard python tooling
+
   :pypi:`pytest-dbt-adapter`
      *last release*: Nov 24, 2021,
      *status*: N/A,
@@ -2323,6 +3930,34 @@ This list contains 963 plugins.
 
      A pytest plugin for testing dbt adapter plugins
 
+  :pypi:`pytest-dbt-conventions`
+     *last release*: Mar 02, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=6.2.5,<7.0.0)
+
+     A pytest plugin for linting a dbt project's conventions
+
+  :pypi:`pytest-dbt-core`
+     *last release*: Jun 04, 2024,
+     *status*: N/A,
+     *requires*: pytest>=6.2.5; extra == "test"
+
+     Pytest extension for dbt.
+
+  :pypi:`pytest-dbt-duckdb`
+     *last release*: Feb 09, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.3.4
+
+     Fearless testing for dbt models, powered by DuckDB.
+
+  :pypi:`pytest-dbt-postgres`
+     *last release*: Sep 03, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.3.2
+
+     Pytest tooling to unittest DBT & Postgres models
+
   :pypi:`pytest-dbus-notification`
      *last release*: Mar 05, 2014,
      *status*: 5 - Production/Stable,
@@ -2330,6 +3965,20 @@ This list contains 963 plugins.
 
      D-BUS notifications for pytest results.
 
+  :pypi:`pytest-dbx`
+     *last release*: Nov 29, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.1.3,<8.0.0)
+
+     Pytest plugin to run unit tests for dbx (Databricks CLI extensions) related code
+
+  :pypi:`pytest-dc`
+     *last release*: Aug 16, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest >=3.3
+
+     Manages Docker containers during your integration tests
+
   :pypi:`pytest-deadfixtures`
      *last release*: Jul 23, 2020,
      *status*: 5 - Production/Stable,
@@ -2337,6 +3986,13 @@ This list contains 963 plugins.
 
      A simple plugin to list unused fixtures in pytest
 
+  :pypi:`pytest-deduplicate`
+     *last release*: Aug 12, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Identifies duplicate unit tests
+
   :pypi:`pytest-deepcov`
      *last release*: Mar 30, 2021,
      *status*: N/A,
@@ -2344,12 +4000,12 @@ This list contains 963 plugins.
 
      deepcov
 
-  :pypi:`pytest-defer`
-     *last release*: Aug 24, 2021,
+  :pypi:`pytest_defer`
+     *last release*: Nov 13, 2024,
      *status*: N/A,
-     *requires*: N/A
-
+     *requires*: pytest>=8.3
 
+     A 'defer' fixture for pytest
 
   :pypi:`pytest-demo-plugin`
      *last release*: May 15, 2021,
@@ -2359,7 +4015,7 @@ This list contains 963 plugins.
      pytest示例插件
 
   :pypi:`pytest-dependency`
-     *last release*: Feb 14, 2020,
+     *last release*: Dec 31, 2023,
      *status*: 4 - Beta,
      *requires*: N/A
 
@@ -2379,10 +4035,17 @@ This list contains 963 plugins.
 
      Mark tests as testing a deprecated feature with a warning note.
 
-  :pypi:`pytest-describe`
-     *last release*: Nov 13, 2021,
+  :pypi:`pytest-deprecator`
+     *last release*: Dec 02, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=4.0.0)
+     *requires*: pytest>=6.2.0
+
+     A simple plugin to use with pytest
+
+  :pypi:`pytest-describe`
+     *last release*: Feb 10, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest <9,>=4.6
 
      Describe-style plugin for pytest
 
@@ -2393,13 +4056,27 @@ This list contains 963 plugins.
 
      plugin for rich text descriptions
 
+  :pypi:`pytest-deselect-if`
+     *last release*: Dec 26, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A plugin to deselect pytests tests rather than using skipif
+
   :pypi:`pytest-devpi-server`
-     *last release*: May 28, 2019,
+     *last release*: Oct 17, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      DevPI server fixture for py.test
 
+  :pypi:`pytest-dhos`
+     *last release*: Sep 07, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Common fixtures for pytest in DHOS services and libraries
+
   :pypi:`pytest-diamond`
      *last release*: Aug 31, 2015,
      *status*: 4 - Beta,
@@ -2428,6 +4105,41 @@ This list contains 963 plugins.
 
      A simple plugin to use with pytest
 
+  :pypi:`pytest-diffeo`
+     *last release*: Feb 20, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     A package to prevent Dependency Confusion attacks against Yandex.
+
+  :pypi:`pytest-diff-selector`
+     *last release*: Feb 24, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=6.2.2) ; extra == 'all'
+
+     Get tests affected by code changes (using git)
+
+  :pypi:`pytest-difido`
+     *last release*: Oct 23, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=4.0.0)
+
+     PyTest plugin for generating Difido reports
+
+  :pypi:`pytest-dir-equal`
+     *last release*: Dec 11, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.3.2
+
+     pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing
+
+  :pypi:`pytest-dirty`
+     *last release*: Jul 11, 2024,
+     *status*: 3 - Alpha,
+     *requires*: pytest>=8.2; extra == "dev"
+
+     Static import analysis for thrifty testing.
+
   :pypi:`pytest-disable`
      *last release*: Sep 10, 2015,
      *status*: 4 - Beta,
@@ -2443,16 +4155,44 @@ This list contains 963 plugins.
      Disable plugins per test
 
   :pypi:`pytest-discord`
-     *last release*: Mar 20, 2021,
-     *status*: 3 - Alpha,
-     *requires*: pytest (!=6.0.0,<7,>=3.3.2)
+     *last release*: May 11, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest!=6.0.0,<9,>=3.3.2
 
      A pytest plugin to notify test results to a Discord channel.
 
+  :pypi:`pytest-discover`
+     *last release*: Mar 26, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest plugin to record discovered tests in a file
+
+  :pypi:`pytest-ditto`
+     *last release*: Jun 09, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=3.5.0
+
+     Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats.
+
+  :pypi:`pytest-ditto-pandas`
+     *last release*: May 29, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=3.5.0
+
+     pytest-ditto plugin for pandas snapshots.
+
+  :pypi:`pytest-ditto-pyarrow`
+     *last release*: Jun 09, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=3.5.0
+
+     pytest-ditto plugin for pyarrow tables.
+
   :pypi:`pytest-django`
-     *last release*: Dec 02, 2021,
+     *last release*: Apr 03, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.4.0)
+     *requires*: pytest>=7.0.0
 
      A Django plugin for pytest.
 
@@ -2464,9 +4204,9 @@ This list contains 963 plugins.
      A Django plugin for pytest.
 
   :pypi:`pytest-djangoapp`
-     *last release*: Aug 04, 2021,
+     *last release*: May 19, 2023,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest
 
      Nice pytest plugin to help you with Django pluggable application testing.
 
@@ -2484,6 +4224,20 @@ This list contains 963 plugins.
 
      Integrate CasperJS with your django tests as a pytest fixture.
 
+  :pypi:`pytest-django-class`
+     *last release*: Aug 08, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     A pytest plugin for running django in class-scoped fixtures
+
+  :pypi:`pytest-django-docker-pg`
+     *last release*: Jun 13, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9.0.0,>=7.0.0
+
+
+
   :pypi:`pytest-django-dotenv`
      *last release*: Nov 26, 2019,
      *status*: 4 - Beta,
@@ -2498,6 +4252,13 @@ This list contains 963 plugins.
 
      Factories for your Django models that can be used as Pytest fixtures.
 
+  :pypi:`pytest-django-filefield`
+     *last release*: May 09, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest >= 5.2
+
+     Replaces FileField.storage with something you can patch globally.
+
   :pypi:`pytest-django-gcir`
      *last release*: Mar 06, 2018,
      *status*: 5 - Production/Stable,
@@ -2513,8 +4274,8 @@ This list contains 963 plugins.
      Cleanup your Haystack indexes between tests
 
   :pypi:`pytest-django-ifactory`
-     *last release*: Jan 13, 2021,
-     *status*: 3 - Alpha,
+     *last release*: Aug 27, 2023,
+     *status*: 5 - Production/Stable,
      *requires*: N/A
 
      A model instance factory for pytest-django
@@ -2527,7 +4288,7 @@ This list contains 963 plugins.
      The bare minimum to integrate py.test with Django.
 
   :pypi:`pytest-django-liveserver-ssl`
-     *last release*: Jul 30, 2021,
+     *last release*: Jan 09, 2025,
      *status*: 3 - Alpha,
      *requires*: N/A
 
@@ -2576,8 +4337,8 @@ This list contains 963 plugins.
      py.test plugin for reporting the number of SQLs executed per django testcase.
 
   :pypi:`pytest-django-testing-postgresql`
-     *last release*: Dec 05, 2019,
-     *status*: 3 - Alpha,
+     *last release*: Jan 31, 2022,
+     *status*: 4 - Beta,
      *requires*: N/A
 
      Use a temporary PostgreSQL database with pytest-django
@@ -2589,6 +4350,13 @@ This list contains 963 plugins.
 
      A documentation plugin for py.test.
 
+  :pypi:`pytest-docfiles`
+     *last release*: Dec 22, 2021,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.7.0)
+
+     pytest plugin to test codeblocks in your documentation.
+
   :pypi:`pytest-docgen`
      *last release*: Apr 17, 2020,
      *status*: N/A,
@@ -2597,11 +4365,18 @@ This list contains 963 plugins.
      An RST Documentation Generator for pytest-based test suites
 
   :pypi:`pytest-docker`
-     *last release*: Jun 14, 2021,
+     *last release*: Feb 06, 2025,
      *status*: N/A,
-     *requires*: pytest (<7.0,>=4.0)
+     *requires*: pytest<9.0,>=4.0
 
-     Simple pytest fixtures for Docker and docker-compose based tests
+     Simple pytest fixtures for Docker and Docker Compose based tests
+
+  :pypi:`pytest-docker-apache-fixtures`
+     *last release*: Aug 12, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Pytest fixtures for testing with apache2 (httpd).
 
   :pypi:`pytest-docker-butla`
      *last release*: Jun 16, 2019,
@@ -2624,6 +4399,13 @@ This list contains 963 plugins.
 
      Manages Docker containers during your integration tests
 
+  :pypi:`pytest-docker-compose-v2`
+     *last release*: Dec 11, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.2.2
+
+     Manages Docker containers during your integration tests
+
   :pypi:`pytest-docker-db`
      *last release*: Mar 20, 2021,
      *status*: 5 - Production/Stable,
@@ -2632,19 +4414,26 @@ This list contains 963 plugins.
      A plugin to use docker databases for pytests
 
   :pypi:`pytest-docker-fixtures`
-     *last release*: Nov 23, 2021,
+     *last release*: Apr 03, 2024,
      *status*: 3 - Alpha,
      *requires*: N/A
 
      pytest docker fixtures
 
   :pypi:`pytest-docker-git-fixtures`
-     *last release*: Mar 11, 2021,
+     *last release*: Aug 12, 2024,
      *status*: 4 - Beta,
      *requires*: pytest
 
      Pytest fixtures for testing with git scm.
 
+  :pypi:`pytest-docker-haproxy-fixtures`
+     *last release*: Aug 12, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Pytest fixtures for testing with haproxy.
+
   :pypi:`pytest-docker-pexpect`
      *last release*: Jan 14, 2019,
      *status*: N/A,
@@ -2667,16 +4456,30 @@ This list contains 963 plugins.
      Easy to use, simple to extend, pytest plugin that minimally leverages docker-py.
 
   :pypi:`pytest-docker-registry-fixtures`
-     *last release*: Mar 04, 2021,
+     *last release*: Aug 12, 2024,
      *status*: 4 - Beta,
      *requires*: pytest
 
      Pytest fixtures for testing with docker registries.
 
+  :pypi:`pytest-docker-service`
+     *last release*: Jan 03, 2024,
+     *status*: 3 - Alpha,
+     *requires*: pytest (>=7.1.3)
+
+     pytest plugin to start docker container
+
+  :pypi:`pytest-docker-squid-fixtures`
+     *last release*: Aug 12, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Pytest fixtures for testing with squid.
+
   :pypi:`pytest-docker-tools`
-     *last release*: Jul 23, 2021,
+     *last release*: Mar 16, 2025,
      *status*: 4 - Beta,
-     *requires*: pytest (>=6.0.1,<7.0.0)
+     *requires*: pytest>=6.0.1
 
      Docker integration tests for pytest
 
@@ -2715,19 +4518,40 @@ This list contains 963 plugins.
 
      A simple pytest plugin to import names and add them to the doctest namespace.
 
+  :pypi:`pytest-doctest-mkdocstrings`
+     *last release*: Mar 02, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Run pytest --doctest-modules with markdown docstrings in code blocks (\`\`\`)
+
   :pypi:`pytest-doctestplus`
-     *last release*: Nov 16, 2021,
-     *status*: 3 - Alpha,
-     *requires*: pytest (>=4.6)
+     *last release*: Jan 25, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=4.6
 
      Pytest plugin with advanced doctest features.
 
-  :pypi:`pytest-doctest-ufunc`
-     *last release*: Aug 02, 2020,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+  :pypi:`pytest-documentary`
+     *last release*: Jul 11, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     A simple pytest plugin to generate test documentation
+
+  :pypi:`pytest-dogu-report`
+     *last release*: Jul 07, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest plugin for dogu report
 
-     A plugin to run doctests in docstrings of Numpy ufuncs
+  :pypi:`pytest-dogu-sdk`
+     *last release*: Dec 14, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest plugin for the Dogu
 
   :pypi:`pytest-dolphin`
      *last release*: Nov 30, 2016,
@@ -2736,6 +4560,13 @@ This list contains 963 plugins.
 
      Some extra stuff that we use ininternally
 
+  :pypi:`pytest-donde`
+     *last release*: Oct 01, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest >=7.3.1
+
+     record pytest session characteristics per test item (coverage and duration) into a persistent file and use them in your own plugin or script.
+
   :pypi:`pytest-doorstop`
      *last release*: Jun 09, 2020,
      *status*: 4 - Beta,
@@ -2750,10 +4581,38 @@ This list contains 963 plugins.
 
      A py.test plugin that parses environment files before running tests
 
+  :pypi:`pytest-dot-only-pkcopley`
+     *last release*: Oct 27, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     A Pytest marker for only running a single test
+
+  :pypi:`pytest-dparam`
+     *last release*: Aug 27, 2024,
+     *status*: 6 - Mature,
+     *requires*: pytest
+
+     A more readable alternative to @pytest.mark.parametrize.
+
+  :pypi:`pytest-dpg`
+     *last release*: Aug 13, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest-dpg is a pytest plugin for testing Dear PyGui (DPG) applications
+
+  :pypi:`pytest-draw`
+     *last release*: Mar 21, 2023,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     Pytest plugin for randomly selecting a specific number of tests
+
   :pypi:`pytest-drf`
-     *last release*: Nov 12, 2020,
+     *last release*: Jul 12, 2022,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.6)
+     *requires*: pytest (>=3.7)
 
      A Django REST framework plugin for pytest.
 
@@ -2765,14 +4624,28 @@ This list contains 963 plugins.
      Tool to allow webdriver automation to be ran locally or remotely
 
   :pypi:`pytest-drop-dup-tests`
-     *last release*: May 23, 2020,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=2.7)
+     *last release*: Mar 04, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest >=7
 
      A Pytest plugin to drop duplicated tests during collection
 
+  :pypi:`pytest-dryci`
+     *last release*: Sep 27, 2024,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Test caching plugin for pytest
+
+  :pypi:`pytest-dryrun`
+     *last release*: Jan 19, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9,>=7.40
+
+     A Pytest plugin to ignore tests during collection without reporting them in the test summary.
+
   :pypi:`pytest-dummynet`
-     *last release*: Oct 13, 2021,
+     *last release*: Dec 15, 2021,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
@@ -2786,12 +4659,19 @@ This list contains 963 plugins.
      A pytest plugin for dumping test results to json.
 
   :pypi:`pytest-duration-insights`
-     *last release*: Jun 25, 2021,
+     *last release*: Jul 15, 2024,
      *status*: N/A,
      *requires*: N/A
 
 
 
+  :pypi:`pytest-durations`
+     *last release*: Mar 18, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=4.6
+
+     Pytest plugin reporting fixtures and test functions execution time.
+
   :pypi:`pytest-dynamicrerun`
      *last release*: Aug 15, 2020,
      *status*: 4 - Beta,
@@ -2800,7 +4680,7 @@ This list contains 963 plugins.
      A pytest plugin to rerun tests dynamically based off of test outcome and output.
 
   :pypi:`pytest-dynamodb`
-     *last release*: Jun 03, 2021,
+     *last release*: Apr 04, 2025,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
@@ -2814,11 +4694,11 @@ This list contains 963 plugins.
      pytest-easy-addoption: Easy way to work with pytest addoption
 
   :pypi:`pytest-easy-api`
-     *last release*: Mar 26, 2018,
+     *last release*: Feb 16, 2024,
      *status*: N/A,
      *requires*: N/A
 
-     Simple API testing with pytest
+     A package to prevent Dependency Confusion attacks against Yandex.
 
   :pypi:`pytest-easyMPI`
      *last release*: Oct 21, 2020,
@@ -2841,6 +4721,13 @@ This list contains 963 plugins.
 
      Pytest plugin for easy testing against servers
 
+  :pypi:`pytest-ebics-sandbox`
+     *last release*: Aug 15, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     A pytest plugin for testing against an EBICS sandbox server. Requires docker.
+
   :pypi:`pytest-ec2`
      *last release*: Oct 22, 2019,
      *status*: 3 - Alpha,
@@ -2849,16 +4736,37 @@ This list contains 963 plugins.
      Pytest execution on EC2 instance
 
   :pypi:`pytest-echo`
-     *last release*: Jan 08, 2020,
+     *last release*: Apr 01, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest>=8.3.3
+
+     pytest plugin that allows to dump environment variables, package version and generic attributes
+
+  :pypi:`pytest-edit`
+     *last release*: Nov 17, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Edit the source code of a failed test with \`pytest --edit\`.
 
-     pytest plugin with mechanisms for echoing environment variables, package version and generic attributes
+  :pypi:`pytest-ekstazi`
+     *last release*: Sep 10, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest plugin to select test using Ekstazi algorithm
 
   :pypi:`pytest-elasticsearch`
-     *last release*: May 12, 2021,
+     *last release*: Dec 03, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.0.0)
+     *requires*: pytest>=7.0
+
+     Elasticsearch fixtures and fixture factories for Pytest.
+
+  :pypi:`pytest-elasticsearch-test`
+     *last release*: Aug 21, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.0
 
      Elasticsearch fixtures and fixture factories for Pytest.
 
@@ -2869,10 +4777,17 @@ This list contains 963 plugins.
 
      Tool to help automate user interfaces
 
+  :pypi:`pytest-eliot`
+     *last release*: Aug 31, 2022,
+     *status*: 1 - Planning,
+     *requires*: pytest (>=5.4.0)
+
+     An eliot plugin for pytest.
+
   :pypi:`pytest-elk-reporter`
-     *last release*: Jan 24, 2021,
+     *last release*: Jul 25, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest>=3.5.0
 
      A simple plugin to use with pytest
 
@@ -2884,53 +4799,74 @@ This list contains 963 plugins.
      Send execution result email
 
   :pypi:`pytest-embedded`
-     *last release*: Nov 29, 2021,
-     *status*: N/A,
-     *requires*: pytest (>=6.2.0)
+     *last release*: Mar 13, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.0
+
+     A pytest plugin that designed for embedded testing.
+
+  :pypi:`pytest-embedded-arduino`
+     *last release*: Mar 13, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
 
-     pytest embedded plugin
+     Make pytest-embedded plugin work with Arduino.
 
   :pypi:`pytest-embedded-idf`
-     *last release*: Nov 29, 2021,
-     *status*: N/A,
+     *last release*: Mar 13, 2025,
+     *status*: 5 - Production/Stable,
      *requires*: N/A
 
-     pytest embedded plugin for esp-idf project
+     Make pytest-embedded plugin work with ESP-IDF.
 
   :pypi:`pytest-embedded-jtag`
-     *last release*: Nov 29, 2021,
-     *status*: N/A,
+     *last release*: Mar 13, 2025,
+     *status*: 5 - Production/Stable,
      *requires*: N/A
 
-     pytest embedded plugin for testing with jtag
+     Make pytest-embedded plugin work with JTAG.
 
-  :pypi:`pytest-embedded-qemu`
-     *last release*: Nov 29, 2021,
-     *status*: N/A,
+  :pypi:`pytest-embedded-nuttx`
+     *last release*: Mar 13, 2025,
+     *status*: 5 - Production/Stable,
      *requires*: N/A
 
-     pytest embedded plugin for qemu, not target chip
+     Make pytest-embedded plugin work with NuttX.
 
-  :pypi:`pytest-embedded-qemu-idf`
-     *last release*: Jun 29, 2021,
-     *status*: N/A,
+  :pypi:`pytest-embedded-qemu`
+     *last release*: Mar 13, 2025,
+     *status*: 5 - Production/Stable,
      *requires*: N/A
 
-     pytest embedded plugin for esp-idf project by qemu, not target chip
+     Make pytest-embedded plugin work with QEMU.
 
   :pypi:`pytest-embedded-serial`
-     *last release*: Nov 29, 2021,
-     *status*: N/A,
+     *last release*: Mar 13, 2025,
+     *status*: 5 - Production/Stable,
      *requires*: N/A
 
-     pytest embedded plugin for testing serial ports
+     Make pytest-embedded plugin work with Serial.
 
   :pypi:`pytest-embedded-serial-esp`
-     *last release*: Nov 29, 2021,
-     *status*: N/A,
+     *last release*: Mar 13, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Make pytest-embedded plugin work with Espressif target boards.
+
+  :pypi:`pytest-embedded-wokwi`
+     *last release*: Mar 13, 2025,
+     *status*: 5 - Production/Stable,
      *requires*: N/A
 
-     pytest embedded plugin for testing espressif boards via serial ports
+     Make pytest-embedded plugin work with the Wokwi CLI.
+
+  :pypi:`pytest-embrace`
+     *last release*: Mar 25, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.0,<8.0)
+
+     💝  Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate.
 
   :pypi:`pytest-emoji`
      *last release*: Feb 19, 2019,
@@ -2940,16 +4876,16 @@ This list contains 963 plugins.
      A pytest plugin that adds emojis to your test result report
 
   :pypi:`pytest-emoji-output`
-     *last release*: Oct 10, 2021,
+     *last release*: Apr 09, 2023,
      *status*: 4 - Beta,
-     *requires*: pytest (==6.0.1)
+     *requires*: pytest (==7.0.1)
 
      Pytest plugin to represent test output with emoji support
 
   :pypi:`pytest-enabler`
-     *last release*: Nov 08, 2021,
+     *last release*: Sep 12, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=6) ; extra == 'testing'
+     *requires*: pytest!=8.1.*,>=6; extra == "test"
 
      Enable installed pytest plugins
 
@@ -2967,6 +4903,27 @@ This list contains 963 plugins.
 
      set your encoding and logger
 
+  :pypi:`pytest-encoding`
+     *last release*: Aug 11, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     set your encoding and logger
+
+  :pypi:`pytest_energy_reporter`
+     *last release*: Mar 28, 2024,
+     *status*: 3 - Alpha,
+     *requires*: pytest<9.0.0,>=8.1.1
+
+     An energy estimation reporter for pytest
+
+  :pypi:`pytest-enhanced-reports`
+     *last release*: Dec 15, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Enhanced test reports for pytest
+
   :pypi:`pytest-enhancements`
      *last release*: Oct 30, 2019,
      *status*: 4 - Beta,
@@ -2975,11 +4932,11 @@ This list contains 963 plugins.
      Improvements for pytest (rejected upstream)
 
   :pypi:`pytest-env`
-     *last release*: Jun 16, 2017,
-     *status*: 4 - Beta,
-     *requires*: N/A
+     *last release*: Sep 17, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=8.3.3
 
-     py.test plugin that allows you to add environment variables.
+     pytest plugin that allows you to add environment variables.
 
   :pypi:`pytest-envfiles`
      *last release*: Oct 08, 2015,
@@ -2995,6 +4952,13 @@ This list contains 963 plugins.
 
      Push information about the running pytest into envvars
 
+  :pypi:`pytest-environment`
+     *last release*: Mar 17, 2024,
+     *status*: 1 - Planning,
+     *requires*: N/A
+
+     Pytest Environment
+
   :pypi:`pytest-envraw`
      *last release*: Aug 27, 2020,
      *status*: 4 - Beta,
@@ -3023,6 +4987,13 @@ This list contains 963 plugins.
 
      pytest plugin to check for commented out code
 
+  :pypi:`pytest_erp`
+     *last release*: Jan 13, 2015,
+     *status*: N/A,
+     *requires*: N/A
+
+     py.test plugin to send test info to report portal dynamically
+
   :pypi:`pytest-error-for-skips`
      *last release*: Dec 19, 2019,
      *status*: 4 - Beta,
@@ -3030,6 +5001,13 @@ This list contains 963 plugins.
 
      Pytest plugin to treat skipped tests a test failure
 
+  :pypi:`pytest-errxfail`
+     *last release*: Jan 06, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     pytest plugin to mark a test as xfailed if it fails with the specified error message in the captured output
+
   :pypi:`pytest-eth`
      *last release*: Aug 14, 2020,
      *status*: 1 - Planning,
@@ -3045,12 +5023,19 @@ This list contains 963 plugins.
      pytest-ethereum: Pytest library for ethereum projects.
 
   :pypi:`pytest-eucalyptus`
-     *last release*: Aug 13, 2019,
+     *last release*: Jun 28, 2022,
      *status*: N/A,
      *requires*: pytest (>=4.2.0)
 
      Pytest Plugin for BDD
 
+  :pypi:`pytest-evals`
+     *last release*: Feb 02, 2025,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
+
+     A pytest plugin for running and analyzing LLM evaluation tests
+
   :pypi:`pytest-eventlet`
      *last release*: Oct 04, 2021,
      *status*: N/A,
@@ -3058,10 +5043,66 @@ This list contains 963 plugins.
 
      Applies eventlet monkey-patch as a pytest plugin.
 
+  :pypi:`pytest_evm`
+     *last release*: Sep 23, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest<9.0.0,>=8.1.1
+
+     The testing package containing tools to test Web3-based projects
+
+  :pypi:`pytest_exact_fixtures`
+     *last release*: Feb 04, 2019,
+     *status*: N/A,
+     *requires*: N/A
+
+     Parse queries in Lucene and Elasticsearch syntaxes
+
+  :pypi:`pytest-examples`
+     *last release*: Mar 23, 2025,
+     *status*: N/A,
+     *requires*: pytest>=7
+
+     Pytest plugin for testing examples in docstrings and markdown files.
+
+  :pypi:`pytest-exasol-backend`
+     *last release*: Feb 11, 2025,
+     *status*: N/A,
+     *requires*: pytest<9,>=7
+
+
+
+  :pypi:`pytest-exasol-extension`
+     *last release*: Feb 11, 2025,
+     *status*: N/A,
+     *requires*: pytest<9,>=7
+
+
+
+  :pypi:`pytest-exasol-itde`
+     *last release*: Nov 22, 2024,
+     *status*: N/A,
+     *requires*: pytest<9,>=7
+
+
+
+  :pypi:`pytest-exasol-saas`
+     *last release*: Nov 22, 2024,
+     *status*: N/A,
+     *requires*: pytest<9,>=7
+
+
+
+  :pypi:`pytest-exasol-slc`
+     *last release*: Feb 11, 2025,
+     *status*: N/A,
+     *requires*: pytest<9,>=7
+
+
+
   :pypi:`pytest-excel`
-     *last release*: Oct 06, 2020,
+     *last release*: Jun 18, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest>3.6
 
      pytest plugin for generating excel reports
 
@@ -3080,12 +5121,33 @@ This list contains 963 plugins.
      Walk your code through exception script to check it's resiliency to failures.
 
   :pypi:`pytest-executable`
-     *last release*: Nov 10, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (<6.3,>=4.3)
+     *last release*: Oct 07, 2023,
+     *status*: N/A,
+     *requires*: pytest <8,>=5
 
      pytest plugin for testing executables
 
+  :pypi:`pytest-execution-timer`
+     *last release*: Dec 24, 2021,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     A timer for the phases of Pytest's execution.
+
+  :pypi:`pytest-exit-code`
+     *last release*: May 06, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A pytest plugin that overrides the built-in exit codes to retain more information about the test results.
+
+  :pypi:`pytest-exit-status`
+     *last release*: Jan 25, 2025,
+     *status*: N/A,
+     *requires*: pytest>=8.0.0
+
+     Enhance.
+
   :pypi:`pytest-expect`
      *last release*: Apr 21, 2016,
      *status*: 4 - Beta,
@@ -3093,8 +5155,22 @@ This list contains 963 plugins.
 
      py.test plugin to store test expectations and mark tests based on them
 
+  :pypi:`pytest-expectdir`
+     *last release*: Mar 19, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=5.0)
+
+     A pytest plugin to provide initial/expected directories, and check a test transforms the initial directory to the expected one
+
+  :pypi:`pytest-expected`
+     *last release*: Feb 26, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     Record and play back your expectations
+
   :pypi:`pytest-expecter`
-     *last release*: Jul 08, 2020,
+     *last release*: Sep 18, 2022,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
@@ -3107,6 +5183,20 @@ This list contains 963 plugins.
 
      This plugin is used to expect multiple assert using pytest framework.
 
+  :pypi:`pytest-expect-test`
+     *last release*: Apr 10, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     A fixture to support expect tests in pytest
+
+  :pypi:`pytest-experiments`
+     *last release*: Dec 13, 2021,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=6.2.5,<7.0.0)
+
+     A pytest plugin to help developers of research-oriented software projects keep track of the results of their numerical experiments.
+
   :pypi:`pytest-explicit`
      *last release*: Jun 15, 2021,
      *status*: 5 - Production/Stable,
@@ -3115,12 +5205,40 @@ This list contains 963 plugins.
      A Pytest plugin to ignore certain marked tests by default
 
   :pypi:`pytest-exploratory`
-     *last release*: Aug 03, 2021,
+     *last release*: Sep 18, 2024,
      *status*: N/A,
-     *requires*: pytest (>=5.3)
+     *requires*: pytest>=6.2
 
      Interactive console for pytest.
 
+  :pypi:`pytest-explorer`
+     *last release*: Aug 01, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     terminal ui for exploring and running tests
+
+  :pypi:`pytest-ext`
+     *last release*: Mar 31, 2024,
+     *status*: N/A,
+     *requires*: pytest>=5.3
+
+     pytest plugin for automation test
+
+  :pypi:`pytest-extended-mock`
+     *last release*: Mar 12, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.3.5
+
+     a pytest extension for easy mock setup
+
+  :pypi:`pytest-extensions`
+     *last release*: Aug 17, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest ; extra == 'testing'
+
+     A collection of helpers for pytest to ease testing
+
   :pypi:`pytest-external-blockers`
      *last release*: Oct 05, 2021,
      *status*: N/A,
@@ -3128,6 +5246,13 @@ This list contains 963 plugins.
 
      a special outcome for tests that are blocked for external reasons
 
+  :pypi:`pytest_extra`
+     *last release*: Aug 14, 2014,
+     *status*: N/A,
+     *requires*: N/A
+
+     Some helpers for writing tests with pytest.
+
   :pypi:`pytest-extra-durations`
      *last release*: Apr 21, 2020,
      *status*: 4 - Beta,
@@ -3135,6 +5260,20 @@ This list contains 963 plugins.
 
      A pytest plugin to get durations on a per-function basis and per module basis.
 
+  :pypi:`pytest-extra-markers`
+     *last release*: Mar 05, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Additional pytest markers to dynamically enable/disable tests viia CLI flags
+
+  :pypi:`pytest-f3ts`
+     *last release*: Feb 21, 2025,
+     *status*: N/A,
+     *requires*: pytest<8.0.0,>=7.2.1
+
+     Pytest Plugin for communicating test results and information to a FixturFab Test Runner GUI
+
   :pypi:`pytest-fabric`
      *last release*: Sep 12, 2018,
      *status*: 5 - Production/Stable,
@@ -3142,6 +5281,13 @@ This list contains 963 plugins.
 
      Provides test utilities to run fabric task tests by using docker containers
 
+  :pypi:`pytest-factor`
+     *last release*: Feb 20, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     A package to prevent Dependency Confusion attacks against Yandex.
+
   :pypi:`pytest-factory`
      *last release*: Sep 06, 2020,
      *status*: 3 - Alpha,
@@ -3150,9 +5296,9 @@ This list contains 963 plugins.
      Use factories for test setup with py.test
 
   :pypi:`pytest-factoryboy`
-     *last release*: Dec 30, 2020,
+     *last release*: Mar 05, 2024,
      *status*: 6 - Mature,
-     *requires*: pytest (>=4.6)
+     *requires*: pytest (>=6.2)
 
      Factory Boy support for pytest.
 
@@ -3164,12 +5310,19 @@ This list contains 963 plugins.
      Generates pytest fixtures that allow the use of type hinting
 
   :pypi:`pytest-factoryboy-state`
-     *last release*: Dec 11, 2020,
-     *status*: 4 - Beta,
+     *last release*: Mar 22, 2022,
+     *status*: 5 - Production/Stable,
      *requires*: pytest (>=5.0)
 
      Simple factoryboy random state management
 
+  :pypi:`pytest-failed-screen-record`
+     *last release*: Jan 05, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=7.1.2d,<8.0.0)
+
+     Create a video of the screen when pytest fails
+
   :pypi:`pytest-failed-screenshot`
      *last release*: Apr 21, 2021,
      *status*: N/A,
@@ -3184,6 +5337,20 @@ This list contains 963 plugins.
 
      A pytest plugin that helps better distinguishing real test failures from setup flakiness.
 
+  :pypi:`pytest-fail-slow`
+     *last release*: Jun 01, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.0
+
+     Fail tests that take too long to run
+
+  :pypi:`pytest-failure-tracker`
+     *last release*: Jul 17, 2024,
+     *status*: N/A,
+     *requires*: pytest>=6.0.0
+
+     A pytest plugin for tracking test failures over multiple runs
+
   :pypi:`pytest-faker`
      *last release*: Dec 19, 2016,
      *status*: 6 - Mature,
@@ -3199,11 +5366,11 @@ This list contains 963 plugins.
      Pytest helpers for Falcon.
 
   :pypi:`pytest-falcon-client`
-     *last release*: Mar 19, 2019,
+     *last release*: Feb 21, 2024,
      *status*: N/A,
      *requires*: N/A
 
-     Pytest \`client\` fixture for the Falcon Framework
+     A package to prevent Dependency Confusion attacks against Yandex.
 
   :pypi:`pytest-fantasy`
      *last release*: Mar 14, 2019,
@@ -3219,15 +5386,22 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-fastapi-deps`
+     *last release*: Jul 20, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     A fixture which allows easy replacement of fastapi dependencies for testing
+
   :pypi:`pytest-fastest`
-     *last release*: Mar 05, 2020,
-     *status*: N/A,
-     *requires*: N/A
+     *last release*: Oct 04, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=4.4)
 
      Use SCM and coverage to run only needed tests
 
   :pypi:`pytest-fast-first`
-     *last release*: Apr 02, 2021,
+     *last release*: Jan 19, 2023,
      *status*: 3 - Alpha,
      *requires*: pytest
 
@@ -3240,6 +5414,13 @@ This list contains 963 plugins.
 
      py.test plugin that activates the fault handler module for tests (dummy package)
 
+  :pypi:`pytest-fauna`
+     *last release*: Jan 03, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     A collection of helpful test fixtures for Fauna DB.
+
   :pypi:`pytest-fauxfactory`
      *last release*: Dec 06, 2017,
      *status*: 5 - Production/Stable,
@@ -3254,6 +5435,13 @@ This list contains 963 plugins.
 
      py.test figleaf coverage plugin
 
+  :pypi:`pytest-file`
+     *last release*: Mar 18, 2024,
+     *status*: 1 - Planning,
+     *requires*: N/A
+
+     Pytest File
+
   :pypi:`pytest-filecov`
      *last release*: Jun 27, 2021,
      *status*: 4 - Beta,
@@ -3262,11 +5450,11 @@ This list contains 963 plugins.
      A pytest plugin to detect unused files
 
   :pypi:`pytest-filedata`
-     *last release*: Jan 17, 2019,
-     *status*: 4 - Beta,
+     *last release*: Apr 29, 2024,
+     *status*: 5 - Production/Stable,
      *requires*: N/A
 
-     easily load data from files
+     easily load test data from files
 
   :pypi:`pytest-filemarker`
      *last release*: Dec 01, 2020,
@@ -3275,6 +5463,13 @@ This list contains 963 plugins.
 
      A pytest plugin that runs marked tests when files change.
 
+  :pypi:`pytest-file-watcher`
+     *last release*: Mar 23, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest-File-Watcher is a CLI tool that watches for changes in your code and runs pytest on the changed files.
+
   :pypi:`pytest-filter-case`
      *last release*: Nov 05, 2020,
      *status*: N/A,
@@ -3283,16 +5478,16 @@ This list contains 963 plugins.
      run test cases filter by mark
 
   :pypi:`pytest-filter-subpackage`
-     *last release*: Jan 09, 2020,
-     *status*: 3 - Alpha,
-     *requires*: pytest (>=3.0)
+     *last release*: Mar 04, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest >=4.6
 
      Pytest plugin for filtering based on sub-packages
 
   :pypi:`pytest-find-dependencies`
-     *last release*: Apr 21, 2021,
+     *last release*: Mar 16, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest >=4.3.0
 
      A pytest plugin to find dependencies between tests
 
@@ -3304,19 +5499,40 @@ This list contains 963 plugins.
      A pytest plugin to treat non-assertion failures as test errors.
 
   :pypi:`pytest-firefox`
-     *last release*: Aug 08, 2017,
-     *status*: 3 - Alpha,
-     *requires*: pytest (>=3.0.2)
+     *last release*: Feb 28, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+
 
-     pytest plugin to manipulate firefox
+  :pypi:`pytest-fixture-classes`
+     *last release*: Sep 02, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers
+
+  :pypi:`pytest-fixturecollection`
+     *last release*: Feb 22, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest >=3.5.0
+
+     A pytest plugin to collect tests based on fixtures being used by tests
 
   :pypi:`pytest-fixture-config`
-     *last release*: May 28, 2019,
+     *last release*: Oct 17, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      Fixture configuration utils for py.test
 
+  :pypi:`pytest-fixture-forms`
+     *last release*: Dec 06, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=7.0.0
+
+     A pytest plugin for creating fixtures that holds different forms between tests.
+
   :pypi:`pytest-fixture-maker`
      *last release*: Sep 21, 2021,
      *status*: N/A,
@@ -3332,12 +5548,33 @@ This list contains 963 plugins.
      A pytest plugin to add markers based on fixtures used.
 
   :pypi:`pytest-fixture-order`
-     *last release*: Aug 25, 2020,
-     *status*: N/A,
+     *last release*: May 16, 2022,
+     *status*: 5 - Production/Stable,
      *requires*: pytest (>=3.0)
 
      pytest plugin to control fixture evaluation order
 
+  :pypi:`pytest-fixture-ref`
+     *last release*: Nov 17, 2022,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Lets users reference fixtures without name matching magic.
+
+  :pypi:`pytest-fixture-remover`
+     *last release*: Feb 14, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     A LibCST codemod to remove pytest fixtures applied via the usefixtures decorator, as well as its parametrizations.
+
+  :pypi:`pytest-fixture-rtttg`
+     *last release*: Feb 23, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.0.1,<8.0.0)
+
+     Warn or fail on fixture name clash
+
   :pypi:`pytest-fixtures`
      *last release*: May 01, 2019,
      *status*: 5 - Production/Stable,
@@ -3346,7 +5583,7 @@ This list contains 963 plugins.
      Common fixtures for pytest
 
   :pypi:`pytest-fixture-tools`
-     *last release*: Aug 18, 2020,
+     *last release*: Aug 15, 2024,
      *status*: 6 - Mature,
      *requires*: pytest
 
@@ -3360,21 +5597,35 @@ This list contains 963 plugins.
      A pytest plugin to assert type annotations at runtime.
 
   :pypi:`pytest-flake8`
-     *last release*: Dec 16, 2020,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=3.5)
+     *last release*: Nov 09, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.0
 
      pytest plugin to check FLAKE8 requirements
 
   :pypi:`pytest-flake8-path`
-     *last release*: Aug 11, 2021,
+     *last release*: Oct 25, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      A pytest fixture for testing flake8 plugins.
 
+  :pypi:`pytest-flake8-v2`
+     *last release*: Mar 01, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=7.0)
+
+     pytest plugin to check FLAKE8 requirements
+
+  :pypi:`pytest-flake-detection`
+     *last release*: Nov 29, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     Continuously runs your tests to detect flaky tests
+
   :pypi:`pytest-flakefinder`
-     *last release*: Jul 28, 2020,
+     *last release*: Oct 26, 2022,
      *status*: 4 - Beta,
      *requires*: pytest (>=2.7.1)
 
@@ -3395,14 +5646,21 @@ This list contains 963 plugins.
      Flaptastic py.test plugin
 
   :pypi:`pytest-flask`
-     *last release*: Feb 27, 2021,
+     *last release*: Oct 23, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.2)
+     *requires*: pytest >=5.2
 
      A set of py.test fixtures to test Flask applications.
 
+  :pypi:`pytest-flask-ligand`
+     *last release*: Apr 25, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (~=7.3)
+
+     Pytest fixtures and helper functions to use for testing flask-ligand microservices.
+
   :pypi:`pytest-flask-sqlalchemy`
-     *last release*: Apr 04, 2019,
+     *last release*: Apr 30, 2022,
      *status*: 4 - Beta,
      *requires*: pytest (>=3.2.1)
 
@@ -3415,6 +5673,34 @@ This list contains 963 plugins.
 
      Run tests in transactions using pytest, Flask, and SQLalchemy.
 
+  :pypi:`pytest-flexreport`
+     *last release*: Apr 15, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+
+
+  :pypi:`pytest-fluent`
+     *last release*: Aug 14, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.0.0
+
+     A pytest plugin in order to provide logs via fluentd
+
+  :pypi:`pytest-fluentbit`
+     *last release*: Jun 16, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=7.0.0)
+
+     A pytest plugin in order to provide logs via fluentbit
+
+  :pypi:`pytest-fly`
+     *last release*: Mar 20, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     pytest runner and observer
+
   :pypi:`pytest-flyte`
      *last release*: May 03, 2021,
      *status*: N/A,
@@ -3429,6 +5715,13 @@ This list contains 963 plugins.
 
      A pytest plugin that alerts user of failed test cases with screen notifications
 
+  :pypi:`pytest-forbid`
+     *last release*: Mar 07, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.2.2,<8.0.0)
+
+
+
   :pypi:`pytest-forcefail`
      *last release*: May 15, 2018,
      *status*: 4 - Beta,
@@ -3450,6 +5743,27 @@ This list contains 963 plugins.
 
      A pytest plugin to shim pytest commandline options for fowards compatibility
 
+  :pypi:`pytest-frappe`
+     *last release*: Jul 30, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.0.0
+
+     Pytest Frappe Plugin - A set of pytest fixtures to test Frappe applications
+
+  :pypi:`pytest-freethreaded`
+     *last release*: Oct 03, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     pytest plugin for running parallel tests
+
+  :pypi:`pytest-freezeblaster`
+     *last release*: Feb 11, 2025,
+     *status*: N/A,
+     *requires*: pytest>=6.2.5
+
+     Wrap tests with fixtures in freeze_time
+
   :pypi:`pytest-freezegun`
      *last release*: Jul 19, 2020,
      *status*: 4 - Beta,
@@ -3457,6 +5771,13 @@ This list contains 963 plugins.
 
      Wrap tests with fixtures in freeze_time
 
+  :pypi:`pytest-freezer`
+     *last release*: Dec 12, 2024,
+     *status*: N/A,
+     *requires*: pytest>=3.6
+
+     Pytest plugin providing a fixture interface for spulec/freezegun
+
   :pypi:`pytest-freeze-reqs`
      *last release*: Apr 29, 2021,
      *status*: N/A,
@@ -3465,7 +5786,7 @@ This list contains 963 plugins.
      Check if requirement files are frozen
 
   :pypi:`pytest-frozen-uuids`
-     *last release*: Oct 19, 2021,
+     *last release*: Apr 17, 2022,
      *status*: N/A,
      *requires*: pytest (>=3.0)
 
@@ -3478,6 +5799,13 @@ This list contains 963 plugins.
 
      Pytest plugin for measuring function coverage
 
+  :pypi:`pytest-funcnodes`
+     *last release*: Mar 19, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     Testing plugin for funcnodes
+
   :pypi:`pytest-funparam`
      *last release*: Dec 02, 2021,
      *status*: 4 - Beta,
@@ -3485,6 +5813,13 @@ This list contains 963 plugins.
 
      An alternative way to parametrize test cases.
 
+  :pypi:`pytest-fv`
+     *last release*: Feb 27, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     pytest extensions to support running functional-verification jobs
+
   :pypi:`pytest-fxa`
      *last release*: Aug 28, 2018,
      *status*: 5 - Production/Stable,
@@ -3492,6 +5827,13 @@ This list contains 963 plugins.
 
      pytest plugin for Firefox Accounts
 
+  :pypi:`pytest-fxa-mte`
+     *last release*: Oct 02, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     pytest plugin for Firefox Accounts
+
   :pypi:`pytest-fxtest`
      *last release*: Oct 27, 2020,
      *status*: N/A,
@@ -3499,6 +5841,34 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-fzf`
+     *last release*: Jan 06, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.0.0
+
+     fzf-based test selector for pytest
+
+  :pypi:`pytest_gae`
+     *last release*: Aug 03, 2016,
+     *status*: 3 - Alpha,
+     *requires*: N/A
+
+     pytest plugin for apps written with Google's AppEngine
+
+  :pypi:`pytest-gak`
+     *last release*: Mar 29, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     A Pytest plugin and command line tool for interactive testing with Pytest
+
+  :pypi:`pytest-gather-fixtures`
+     *last release*: Aug 18, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
+
+     set up asynchronous pytest fixtures concurrently
+
   :pypi:`pytest-gc`
      *last release*: Feb 01, 2018,
      *status*: N/A,
@@ -3513,6 +5883,20 @@ This list contains 963 plugins.
 
      Uses gcov to measure test coverage of a C library
 
+  :pypi:`pytest-gcs`
+     *last release*: Jan 24, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=6.2
+
+     GCS fixtures and fixture factories for Pytest.
+
+  :pypi:`pytest-gee`
+     *last release*: Feb 20, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     The Python plugin for your GEE based packages.
+
   :pypi:`pytest-gevent`
      *last release*: Feb 25, 2020,
      *status*: N/A,
@@ -3527,6 +5911,13 @@ This list contains 963 plugins.
 
      A flexible framework for executing BDD gherkin tests
 
+  :pypi:`pytest-gh-log-group`
+     *last release*: Jan 11, 2022,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     pytest plugin for gh actions
+
   :pypi:`pytest-ghostinspector`
      *last release*: May 17, 2016,
      *status*: 3 - Alpha,
@@ -3535,19 +5926,26 @@ This list contains 963 plugins.
      For finding/executing Ghost Inspector tests
 
   :pypi:`pytest-girder`
-     *last release*: Nov 30, 2021,
+     *last release*: Apr 04, 2025,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest>=3.6
 
      A set of pytest fixtures for testing Girder applications.
 
   :pypi:`pytest-git`
-     *last release*: May 28, 2019,
+     *last release*: Oct 17, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      Git repository fixture for py.test
 
+  :pypi:`pytest-gitconfig`
+     *last release*: Aug 11, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.1.2
+
+     Provide a Git config sandbox for testing
+
   :pypi:`pytest-gitcov`
      *last release*: Jan 11, 2020,
      *status*: 2 - Pre-Alpha,
@@ -3555,6 +5953,13 @@ This list contains 963 plugins.
 
      Pytest plugin for reporting on coverage of the last git commit.
 
+  :pypi:`pytest-git-diff`
+     *last release*: Apr 02, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest plugin that allows the user to select the tests affected by a range of git commits
+
   :pypi:`pytest-git-fixtures`
      *last release*: Mar 11, 2021,
      *status*: 4 - Beta,
@@ -3570,12 +5975,19 @@ This list contains 963 plugins.
      Plugin for py.test that associates tests with github issues using a marker.
 
   :pypi:`pytest-github-actions-annotate-failures`
-     *last release*: Oct 24, 2021,
-     *status*: N/A,
-     *requires*: pytest (>=4.0.0)
+     *last release*: Jan 17, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=6.0.0
 
      pytest plugin to annotate failed tests with a workflow command for GitHub Actions
 
+  :pypi:`pytest-github-report`
+     *last release*: Jun 03, 2022,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Generate a GitHub report using pytest in GitHub Workflows
+
   :pypi:`pytest-gitignore`
      *last release*: Jul 17, 2015,
      *status*: 4 - Beta,
@@ -3583,10 +5995,45 @@ This list contains 963 plugins.
 
      py.test plugin to ignore the same files as git
 
+  :pypi:`pytest-gitlab`
+     *last release*: Oct 16, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest Plugin for Gitlab
+
+  :pypi:`pytest-gitlabci-parallelized`
+     *last release*: Mar 08, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     Parallelize pytest across GitLab CI workers.
+
+  :pypi:`pytest-gitlab-code-quality`
+     *last release*: Sep 09, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8.1.1
+
+     Collects warnings while testing and generates a GitLab Code Quality Report.
+
+  :pypi:`pytest-gitlab-fold`
+     *last release*: Dec 31, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest >=2.6.0
+
+     Folds output sections in GitLab CI build log
+
+  :pypi:`pytest-git-selector`
+     *last release*: Nov 17, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Utility to select tests that have had its dependencies modified (as identified by git diff)
+
   :pypi:`pytest-glamor-allure`
-     *last release*: Nov 26, 2021,
+     *last release*: Apr 30, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest
+     *requires*: pytest<=8.2.0
 
      Extends allure-pytest functionality
 
@@ -3598,12 +6045,26 @@ This list contains 963 plugins.
      Pytest fixtures for testing with gnupg.
 
   :pypi:`pytest-golden`
-     *last release*: Nov 23, 2020,
+     *last release*: Jul 18, 2022,
      *status*: N/A,
-     *requires*: pytest (>=6.1.2,<7.0.0)
+     *requires*: pytest (>=6.1.2)
 
      Plugin for pytest that offloads expected outputs to data files
 
+  :pypi:`pytest-goldie`
+     *last release*: May 23, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     A plugin to support golden tests with pytest.
+
+  :pypi:`pytest-google-chat`
+     *last release*: Mar 27, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Notify google chat channel for test results
+
   :pypi:`pytest-graphql-schema`
      *last release*: Oct 18, 2019,
      *status*: N/A,
@@ -3618,6 +6079,13 @@ This list contains 963 plugins.
 
      Green progress dots
 
+  :pypi:`pytest-group-by-class`
+     *last release*: Jun 27, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=2.5)
+
+     A Pytest plugin for running a subset of your tests by splitting them in to groups of classes.
+
   :pypi:`pytest-growl`
      *last release*: Jan 13, 2014,
      *status*: 5 - Production/Stable,
@@ -3632,6 +6100,20 @@ This list contains 963 plugins.
 
      pytest plugin for grpc
 
+  :pypi:`pytest-grunnur`
+     *last release*: Jul 26, 2024,
+     *status*: N/A,
+     *requires*: pytest>=6
+
+     Py.Test plugin for Grunnur-based packages.
+
+  :pypi:`pytest_gui_status`
+     *last release*: Jan 23, 2016,
+     *status*: N/A,
+     *requires*: pytest
+
+     Show pytest status in gui
+
   :pypi:`pytest-hammertime`
      *last release*: Jul 28, 2018,
      *status*: N/A,
@@ -3639,26 +6121,40 @@ This list contains 963 plugins.
 
      Display "🔨 " instead of "." for passed pytest tests.
 
+  :pypi:`pytest-hardware-test-report`
+     *last release*: Apr 01, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest<9.0.0,>=8.0.0
+
+     A simple plugin to use with pytest
+
+  :pypi:`pytest-harmony`
+     *last release*: Jan 17, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.2.1,<8.0.0)
+
+     Chain tests and data with pytest
+
   :pypi:`pytest-harvest`
-     *last release*: Apr 01, 2021,
+     *last release*: Mar 16, 2024,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
      Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes.
 
-  :pypi:`pytest-helm-chart`
-     *last release*: Jun 15, 2020,
+  :pypi:`pytest-helm-charts`
+     *last release*: Oct 31, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=5.4.2,<6.0.0)
+     *requires*: pytest<9.0.0,>=8.0.0
 
      A plugin to provide different types and configs of Kubernetes clusters that can be used for testing.
 
-  :pypi:`pytest-helm-charts`
-     *last release*: Oct 26, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=6.1.2,<7.0.0)
+  :pypi:`pytest-helm-templates`
+     *last release*: Aug 07, 2024,
+     *status*: N/A,
+     *requires*: pytest~=7.4.0; extra == "dev"
 
-     A plugin to provide different types and configs of Kubernetes clusters that can be used for testing.
+     Pytest fixtures for unit testing the output of helm templates
 
   :pypi:`pytest-helper`
      *last release*: May 31, 2019,
@@ -3675,12 +6171,19 @@ This list contains 963 plugins.
      pytest helpers
 
   :pypi:`pytest-helpers-namespace`
-     *last release*: Apr 29, 2021,
+     *last release*: Dec 29, 2021,
      *status*: 5 - Production/Stable,
      *requires*: pytest (>=6.0.0)
 
      Pytest Helpers Namespace Plugin
 
+  :pypi:`pytest-henry`
+     *last release*: Aug 29, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
   :pypi:`pytest-hidecaptured`
      *last release*: May 04, 2018,
      *status*: 4 - Beta,
@@ -3688,6 +6191,13 @@ This list contains 963 plugins.
 
      Hide captured output
 
+  :pypi:`pytest-himark`
+     *last release*: Jun 05, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     This plugin aims to create markers automatically based on a json configuration.
+
   :pypi:`pytest-historic`
      *last release*: Apr 08, 2020,
      *status*: N/A,
@@ -3702,6 +6212,20 @@ This list contains 963 plugins.
 
      Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report
 
+  :pypi:`pytest-history`
+     *last release*: Jan 14, 2024,
+     *status*: N/A,
+     *requires*: pytest (>=7.4.3,<8.0.0)
+
+     Pytest plugin to keep a history of your pytest runs
+
+  :pypi:`pytest-home`
+     *last release*: Jul 28, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Home directory fixtures
+
   :pypi:`pytest-homeassistant`
      *last release*: Aug 12, 2020,
      *status*: 4 - Beta,
@@ -3710,12 +6234,19 @@ This list contains 963 plugins.
      A pytest plugin for use with homeassistant custom components.
 
   :pypi:`pytest-homeassistant-custom-component`
-     *last release*: Nov 20, 2021,
+     *last release*: Apr 05, 2025,
      *status*: 3 - Alpha,
-     *requires*: pytest (==6.2.5)
+     *requires*: pytest==8.3.5
 
      Experimental package to automatically extract test plugins for Home Assistant custom components
 
+  :pypi:`pytest-honey`
+     *last release*: Jan 07, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     A simple plugin to use with pytest
+
   :pypi:`pytest-honors`
      *last release*: Mar 06, 2020,
      *status*: 4 - Beta,
@@ -3723,31 +6254,59 @@ This list contains 963 plugins.
 
      Report on tests that honor constraints, and guard against regressions
 
+  :pypi:`pytest-hot-reloading`
+     *last release*: Sep 23, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
+  :pypi:`pytest-hot-test`
+     *last release*: Dec 10, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     A plugin that tracks test changes
+
+  :pypi:`pytest-houdini`
+     *last release*: Jul 15, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     pytest plugin for testing code in Houdini.
+
   :pypi:`pytest-hoverfly`
-     *last release*: Jul 12, 2021,
+     *last release*: Jan 30, 2023,
      *status*: N/A,
      *requires*: pytest (>=5.0)
 
      Simplify working with Hoverfly from pytest
 
   :pypi:`pytest-hoverfly-wrapper`
-     *last release*: Aug 29, 2021,
-     *status*: 4 - Beta,
-     *requires*: N/A
+     *last release*: Feb 27, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=3.7.0)
 
      Integrates the Hoverfly HTTP proxy into Pytest
 
   :pypi:`pytest-hpfeeds`
-     *last release*: Aug 27, 2021,
+     *last release*: Feb 28, 2023,
      *status*: 4 - Beta,
      *requires*: pytest (>=6.2.4,<7.0.0)
 
      Helpers for testing hpfeeds in your python project
 
   :pypi:`pytest-html`
-     *last release*: Dec 13, 2020,
+     *last release*: Nov 07, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (!=6.0.0,>=5.0)
+     *requires*: pytest>=7.0.0
+
+     pytest plugin for generating HTML reports
+
+  :pypi:`pytest-html-cn`
+     *last release*: Aug 19, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest!=6.0.0,>=5.0
 
      pytest plugin for generating HTML reports
 
@@ -3758,6 +6317,20 @@ This list contains 963 plugins.
 
      optimized pytest plugin for generating HTML reports
 
+  :pypi:`pytest-html-merger`
+     *last release*: Jul 12, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest HTML reports merging utility
+
+  :pypi:`pytest-html-object-storage`
+     *last release*: Jan 17, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Pytest report plugin for send HTML report on object-storage
+
   :pypi:`pytest-html-profiling`
      *last release*: Feb 11, 2020,
      *status*: 5 - Production/Stable,
@@ -3766,12 +6339,19 @@ This list contains 963 plugins.
      Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt.
 
   :pypi:`pytest-html-reporter`
-     *last release*: Apr 25, 2021,
+     *last release*: Feb 13, 2022,
      *status*: N/A,
      *requires*: N/A
 
      Generates a static html report based on pytest framework
 
+  :pypi:`pytest-html-report-merger`
+     *last release*: May 22, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
   :pypi:`pytest-html-thread`
      *last release*: Dec 29, 2020,
      *status*: 5 - Production/Stable,
@@ -3780,19 +6360,26 @@ This list contains 963 plugins.
      pytest plugin for generating HTML reports
 
   :pypi:`pytest-http`
-     *last release*: Dec 05, 2019,
+     *last release*: Aug 22, 2024,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest
 
      Fixture "http" for http requests
 
   :pypi:`pytest-httpbin`
-     *last release*: Feb 11, 2019,
+     *last release*: Sep 18, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest; extra == "test"
 
      Easily test your HTTP library against a local copy of httpbin
 
+  :pypi:`pytest-httpdbg`
+     *last release*: Feb 11, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.0.0
+
+     A pytest plugin to record HTTP(S) requests with stack trace.
+
   :pypi:`pytest-http-mocker`
      *last release*: Oct 20, 2019,
      *status*: N/A,
@@ -3807,27 +6394,41 @@ This list contains 963 plugins.
 
      A thin wrapper of HTTPretty for pytest
 
-  :pypi:`pytest-httpserver`
-     *last release*: Oct 18, 2021,
+  :pypi:`pytest_httpserver`
+     *last release*: Feb 24, 2025,
      *status*: 3 - Alpha,
-     *requires*: pytest ; extra == 'dev'
+     *requires*: N/A
 
      pytest-httpserver is a httpserver for pytest
 
+  :pypi:`pytest-httptesting`
+     *last release*: Dec 19, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8.2.0
+
+     http_testing framework on top of pytest
+
   :pypi:`pytest-httpx`
-     *last release*: Nov 16, 2021,
+     *last release*: Nov 28, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (==6.*)
+     *requires*: pytest==8.*
 
      Send responses to httpx.
 
   :pypi:`pytest-httpx-blockage`
-     *last release*: Nov 16, 2021,
+     *last release*: Feb 16, 2023,
      *status*: N/A,
-     *requires*: pytest (>=6.2.5)
+     *requires*: pytest (>=7.2.1)
 
      Disable httpx requests during a test run
 
+  :pypi:`pytest-httpx-recorder`
+     *last release*: Jan 04, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Recorder feature based on pytest_httpx, like recorder feature in responses.
+
   :pypi:`pytest-hue`
      *last release*: May 09, 2019,
      *status*: N/A,
@@ -3849,17 +6450,24 @@ This list contains 963 plugins.
 
      help hypo module for pytest
 
+  :pypi:`pytest-iam`
+     *last release*: Apr 04, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.0.0
+
+     A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite
+
   :pypi:`pytest-ibutsu`
-     *last release*: Jun 16, 2021,
+     *last release*: Feb 06, 2025,
      *status*: 4 - Beta,
-     *requires*: pytest
+     *requires*: pytest>=7.1
 
      A plugin to sent pytest results to an Ibutsu server
 
   :pypi:`pytest-icdiff`
-     *last release*: Apr 08, 2020,
+     *last release*: Dec 05, 2023,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest
 
      use icdiff for better error messages in pytest assertions
 
@@ -3870,27 +6478,55 @@ This list contains 963 plugins.
 
      A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api
 
+  :pypi:`pytest-idem`
+     *last release*: Dec 13, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     A pytest plugin to help with testing idem projects
+
   :pypi:`pytest-idempotent`
-     *last release*: Nov 26, 2021,
+     *last release*: Jul 25, 2022,
      *status*: N/A,
      *requires*: N/A
 
      Pytest plugin for testing function idempotence.
 
   :pypi:`pytest-ignore-flaky`
-     *last release*: Apr 23, 2021,
+     *last release*: Apr 20, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest>=6.0
 
      ignore failures from flaky tests (pytest plugin)
 
+  :pypi:`pytest-ignore-test-results`
+     *last release*: Feb 03, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.0
+
+     A pytest plugin to ignore test results.
+
   :pypi:`pytest-image-diff`
-     *last release*: Jul 28, 2021,
+     *last release*: Dec 31, 2024,
      *status*: 3 - Alpha,
      *requires*: pytest
 
 
 
+  :pypi:`pytest-image-snapshot`
+     *last release*: Jul 01, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=3.5.0
+
+     A pytest plugin for image snapshot management and comparison.
+
+  :pypi:`pytest-import-check`
+     *last release*: Jul 19, 2024,
+     *status*: 3 - Alpha,
+     *requires*: pytest>=8.1
+
+     pytest plugin to check whether Python modules can be imported
+
   :pypi:`pytest-incremental`
      *last release*: Apr 24, 2021,
      *status*: 5 - Production/Stable,
@@ -3898,12 +6534,26 @@ This list contains 963 plugins.
 
      an incremental test runner (pytest plugin)
 
-  :pypi:`pytest-influxdb`
-     *last release*: Apr 20, 2021,
+  :pypi:`pytest-infinity`
+     *last release*: Jun 09, 2024,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest<9.0.0,>=8.0.0
 
-     Plugin for influxdb and pytest integration.
+
+
+  :pypi:`pytest-influx`
+     *last release*: Oct 16, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.3.3
+
+     Pytest plugin for managing your influx instance between test runs
+
+  :pypi:`pytest-influxdb`
+     *last release*: Apr 20, 2021,
+     *status*: N/A,
+     *requires*: N/A
+
+     Plugin for influxdb and pytest integration.
 
   :pypi:`pytest-info-collector`
      *last release*: May 26, 2019,
@@ -3912,6 +6562,13 @@ This list contains 963 plugins.
 
      pytest plugin to collect information from tests
 
+  :pypi:`pytest-info-plugin`
+     *last release*: Sep 14, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     Get executed interface information in pytest interface automation framework
+
   :pypi:`pytest-informative-node`
      *last release*: Apr 25, 2019,
      *status*: 4 - Beta,
@@ -3919,6 +6576,13 @@ This list contains 963 plugins.
 
      display more node ininformation.
 
+  :pypi:`pytest-infrahouse`
+     *last release*: Mar 18, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest~=8.3
+
+     A set of fixtures to use with pytest
+
   :pypi:`pytest-infrastructure`
      *last release*: Apr 12, 2020,
      *status*: 4 - Beta,
@@ -3927,26 +6591,54 @@ This list contains 963 plugins.
      pytest stack validation prior to testing executing
 
   :pypi:`pytest-ini`
-     *last release*: Sep 30, 2021,
+     *last release*: Apr 26, 2022,
      *status*: N/A,
      *requires*: N/A
 
      Reuse pytest.ini to store env variables
 
+  :pypi:`pytest-initry`
+     *last release*: Apr 30, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.1.1
+
+     Plugin for sending automation test data from Pytest to the initry
+
+  :pypi:`pytest-inline`
+     *last release*: Oct 24, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest<9.0,>=7.0
+
+     A pytest plugin for writing inline tests
+
   :pypi:`pytest-inmanta`
-     *last release*: Aug 17, 2021,
+     *last release*: Oct 10, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest
 
      A py.test plugin providing fixtures to simplify inmanta modules testing.
 
   :pypi:`pytest-inmanta-extensions`
-     *last release*: May 27, 2021,
+     *last release*: Jan 17, 2025,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
      Inmanta tests package
 
+  :pypi:`pytest-inmanta-lsm`
+     *last release*: Dec 13, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Common fixtures for inmanta LSM related modules
+
+  :pypi:`pytest-inmanta-yang`
+     *last release*: Feb 22, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Common fixtures used in inmanta yang related modules
+
   :pypi:`pytest-Inomaly`
      *last release*: Feb 13, 2018,
      *status*: 4 - Beta,
@@ -3954,17 +6646,31 @@ This list contains 963 plugins.
 
      A simple image diff plugin for pytest
 
+  :pypi:`pytest-in-robotframework`
+     *last release*: Nov 23, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     The extension enables easy execution of pytest tests within the Robot Framework environment.
+
+  :pypi:`pytest-insper`
+     *last release*: Mar 21, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest plugin for courses at Insper
+
   :pypi:`pytest-insta`
-     *last release*: Apr 07, 2021,
+     *last release*: Feb 19, 2024,
      *status*: N/A,
-     *requires*: pytest (>=6.0.2,<7.0.0)
+     *requires*: pytest (>=7.2.0,<9.0.0)
 
      A practical snapshot testing plugin for pytest
 
   :pypi:`pytest-instafail`
-     *last release*: Jun 14, 2020,
+     *last release*: Mar 31, 2023,
      *status*: 4 - Beta,
-     *requires*: pytest (>=2.9)
+     *requires*: pytest (>=5)
 
      pytest plugin to show failures instantly
 
@@ -3976,16 +6682,16 @@ This list contains 963 plugins.
      pytest plugin to instrument tests
 
   :pypi:`pytest-integration`
-     *last release*: Apr 16, 2020,
+     *last release*: Nov 17, 2022,
      *status*: N/A,
      *requires*: N/A
 
      Organizing pytests by integration or not
 
   :pypi:`pytest-integration-mark`
-     *last release*: Jul 19, 2021,
+     *last release*: May 22, 2023,
      *status*: N/A,
-     *requires*: pytest (>=5.2,<7.0)
+     *requires*: pytest (>=5.2)
 
      Automatic integration test marking and excluding plugin for pytest
 
@@ -4003,10 +6709,17 @@ This list contains 963 plugins.
 
      Pytest plugin for intercepting outgoing connection requests during pytest run.
 
+  :pypi:`pytest-interface-tester`
+     *last release*: Feb 13, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Pytest plugin for checking charm relation interface protocol compliance.
+
   :pypi:`pytest-invenio`
-     *last release*: May 11, 2021,
+     *last release*: Apr 02, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (<7,>=6)
+     *requires*: pytest<9.0.0,>=6
 
      Pytest fixtures for Invenio.
 
@@ -4017,8 +6730,15 @@ This list contains 963 plugins.
 
      Run tests covering a specific file or changeset
 
+  :pypi:`pytest-iovis`
+     *last release*: Nov 06, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.1.0
+
+     A Pytest plugin to enable Jupyter Notebook testing with Papermill
+
   :pypi:`pytest-ipdb`
-     *last release*: Sep 02, 2014,
+     *last release*: Mar 20, 2013,
      *status*: 2 - Pre-Alpha,
      *requires*: N/A
 
@@ -4031,20 +6751,55 @@ This list contains 963 plugins.
 
      THIS PROJECT IS ABANDONED
 
+  :pypi:`pytest-ipynb2`
+     *last release*: Mar 09, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest plugin to run tests in Jupyter Notebooks
+
+  :pypi:`pytest-ipywidgets`
+     *last release*: Feb 18, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+
+
+  :pypi:`pytest-isolate`
+     *last release*: Jan 16, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Run pytest tests in isolated subprocesses
+
+  :pypi:`pytest-isolate-mpi`
+     *last release*: Feb 24, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=5
+
+     pytest-isolate-mpi allows for MPI-parallel tests being executed in a segfault and MPI_Abort safe manner
+
   :pypi:`pytest-isort`
-     *last release*: Apr 27, 2021,
+     *last release*: Mar 05, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest (>=5.0)
 
      py.test plugin to check import ordering using isort
 
   :pypi:`pytest-it`
-     *last release*: Jan 22, 2020,
+     *last release*: Jan 29, 2024,
      *status*: 4 - Beta,
      *requires*: N/A
 
      Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it.
 
+  :pypi:`pytest-item-dict`
+     *last release*: Nov 14, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.3.0
+
+     Get a hierarchical dict of session.items
+
   :pypi:`pytest-iterassert`
      *last release*: May 11, 2020,
      *status*: 3 - Alpha,
@@ -4052,6 +6807,27 @@ This list contains 963 plugins.
 
      Nicer list and iterable assertion messages for pytest
 
+  :pypi:`pytest-iteration`
+     *last release*: Aug 22, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Add iteration mark for tests
+
+  :pypi:`pytest-iters`
+     *last release*: May 24, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     A contextmanager pytest fixture for handling multiple mock iters
+
+  :pypi:`pytest_jar_yuan`
+     *last release*: Dec 12, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     A allure and pytest used package
+
   :pypi:`pytest-jasmine`
      *last release*: Nov 04, 2017,
      *status*: 1 - Planning,
@@ -4059,6 +6835,13 @@ This list contains 963 plugins.
 
      Run jasmine tests from your pytest test suite
 
+  :pypi:`pytest-jelastic`
+     *last release*: Nov 16, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.2.0,<8.0.0)
+
+     Pytest plugin defining the necessary command-line options to pass to pytests testing a Jelastic environment.
+
   :pypi:`pytest-jest`
      *last release*: May 22, 2018,
      *status*: 4 - Beta,
@@ -4066,20 +6849,41 @@ This list contains 963 plugins.
 
      A custom jest-pytest oriented Pytest reporter
 
+  :pypi:`pytest-jinja`
+     *last release*: Oct 04, 2022,
+     *status*: 3 - Alpha,
+     *requires*: pytest (>=6.2.5,<7.0.0)
+
+     A plugin to generate customizable jinja-based HTML reports in pytest
+
   :pypi:`pytest-jira`
-     *last release*: Dec 02, 2021,
+     *last release*: Apr 30, 2024,
      *status*: 3 - Alpha,
      *requires*: N/A
 
      py.test JIRA integration plugin, using markers
 
+  :pypi:`pytest-jira-xfail`
+     *last release*: Jul 09, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.2.0
+
+     Plugin skips (xfail) tests if unresolved Jira issue(s) linked
+
   :pypi:`pytest-jira-xray`
-     *last release*: Nov 28, 2021,
-     *status*: 3 - Alpha,
-     *requires*: pytest
+     *last release*: Oct 27, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.4
 
      pytest plugin to integrate tests with JIRA XRAY
 
+  :pypi:`pytest-job-selection`
+     *last release*: Jan 30, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     A pytest plugin for load balancing test suites
+
   :pypi:`pytest-jobserver`
      *last release*: May 15, 2019,
      *status*: 5 - Production/Stable,
@@ -4101,6 +6905,20 @@ This list contains 963 plugins.
 
      Generate JSON test reports
 
+  :pypi:`pytest-json-ctrf`
+     *last release*: Oct 10, 2024,
+     *status*: N/A,
+     *requires*: pytest>6.0.0
+
+     Pytest plugin to generate json report in CTRF (Common Test Report Format)
+
+  :pypi:`pytest-json-fixtures`
+     *last release*: Mar 14, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     JSON output for the --fixtures flag
+
   :pypi:`pytest-jsonlint`
      *last release*: Aug 04, 2016,
      *status*: N/A,
@@ -4109,14 +6927,56 @@ This list contains 963 plugins.
      UNKNOWN
 
   :pypi:`pytest-json-report`
-     *last release*: Sep 24, 2021,
+     *last release*: Mar 15, 2022,
      *status*: 4 - Beta,
      *requires*: pytest (>=3.8.0)
 
      A pytest plugin to report test results as JSON files
 
+  :pypi:`pytest-json-report-wip`
+     *last release*: Oct 28, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest >=3.8.0
+
+     A pytest plugin to report test results as JSON files
+
+  :pypi:`pytest-jsonschema`
+     *last release*: Mar 27, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A pytest plugin to perform JSONSchema validations
+
+  :pypi:`pytest-jtr`
+     *last release*: Jul 21, 2024,
+     *status*: N/A,
+     *requires*: pytest<8.0.0,>=7.1.2
+
+     pytest plugin supporting json test report output
+
+  :pypi:`pytest-junit-xray-xml`
+     *last release*: Jan 01, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Export test results in an augmented JUnit format for usage with Xray ()
+
+  :pypi:`pytest-jupyter`
+     *last release*: Apr 04, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.0
+
+     A pytest plugin for testing Jupyter libraries and extensions.
+
+  :pypi:`pytest-jupyterhub`
+     *last release*: Apr 25, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     A reusable JupyterHub pytest plugin
+
   :pypi:`pytest-kafka`
-     *last release*: Aug 24, 2021,
+     *last release*: Aug 14, 2024,
      *status*: N/A,
      *requires*: pytest
 
@@ -4129,8 +6989,43 @@ This list contains 963 plugins.
 
      A plugin to send pytest events to Kafka
 
+  :pypi:`pytest-kairos`
+     *last release*: Aug 08, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=5.0.0
+
+     Pytest plugin with random number generation, reproducibility, and test repetition
+
+  :pypi:`pytest-kasima`
+     *last release*: Jan 26, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=7.2.1,<8.0.0)
+
+     Display horizontal lines above and below the captured standard output for easy viewing.
+
+  :pypi:`pytest-keep-together`
+     *last release*: Dec 07, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Pytest plugin to customize test ordering by running all 'related' tests together
+
+  :pypi:`pytest-kexi`
+     *last release*: Apr 29, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.1.2,<8.0.0)
+
+
+
+  :pypi:`pytest-keyring`
+     *last release*: Dec 08, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8.0.2
+
+     A Pytest plugin to access the system's keyring to provide credentials for tests
+
   :pypi:`pytest-kind`
-     *last release*: Jan 24, 2021,
+     *last release*: Nov 30, 2022,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
@@ -4157,6 +7052,20 @@ This list contains 963 plugins.
 
      Run Konira DSL tests with py.test
 
+  :pypi:`pytest-kookit`
+     *last release*: Sep 10, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Your simple but kooky integration testing with pytest
+
+  :pypi:`pytest-koopmans`
+     *last release*: Nov 21, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     A plugin for testing the koopmans package
+
   :pypi:`pytest-krtech-common`
      *last release*: Nov 28, 2016,
      *status*: 4 - Beta,
@@ -4164,6 +7073,20 @@ This list contains 963 plugins.
 
      pytest krtech common library
 
+  :pypi:`pytest-kubernetes`
+     *last release*: Feb 04, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.3.0
+
+
+
+  :pypi:`pytest-kuunda`
+     *last release*: Feb 25, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest >=6.2.0
+
+     pytest plugin to help with test data setup for PySpark tests
+
   :pypi:`pytest-kwparametrize`
      *last release*: Jan 22, 2021,
      *status*: N/A,
@@ -4172,9 +7095,9 @@ This list contains 963 plugins.
      Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks
 
   :pypi:`pytest-lambda`
-     *last release*: Aug 23, 2021,
-     *status*: 3 - Alpha,
-     *requires*: pytest (>=3.6,<7)
+     *last release*: May 27, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9,>=3.6
 
      Define pytest fixtures with lambda functions.
 
@@ -4185,6 +7108,34 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-langchain`
+     *last release*: Feb 26, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest-style test runner for langchain agents
+
+  :pypi:`pytest-lark`
+     *last release*: Nov 05, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     Create fancy and clear HTML test reports.
+
+  :pypi:`pytest-latin-hypercube`
+     *last release*: Feb 27, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     Implementation of Latin Hypercube Sampling for pytest.
+
+  :pypi:`pytest-launchable`
+     *last release*: Apr 05, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=4.2.0)
+
+     Launchable Pytest Plugin
+
   :pypi:`pytest-layab`
      *last release*: Oct 05, 2020,
      *status*: 5 - Production/Stable,
@@ -4199,6 +7150,13 @@ This list contains 963 plugins.
 
      It helps to use fixtures in pytest.mark.parametrize
 
+  :pypi:`pytest-lazy-fixtures`
+     *last release*: Jan 25, 2025,
+     *status*: N/A,
+     *requires*: pytest>=7
+
+     Allows you to use fixtures in @pytest.mark.parametrize.
+
   :pypi:`pytest-ldap`
      *last release*: Aug 18, 2020,
      *status*: N/A,
@@ -4206,6 +7164,13 @@ This list contains 963 plugins.
 
      python-ldap fixtures for pytest
 
+  :pypi:`pytest-leak-finder`
+     *last release*: Feb 15, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     Find the test that's leaking before the one that fails
+
   :pypi:`pytest-leaks`
      *last release*: Nov 27, 2019,
      *status*: 1 - Planning,
@@ -4213,6 +7178,20 @@ This list contains 963 plugins.
 
      A pytest plugin to trace resource leaks.
 
+  :pypi:`pytest-leaping`
+     *last release*: Mar 27, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A simple plugin to use with pytest
+
+  :pypi:`pytest-leo-interface`
+     *last release*: Mar 19, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest extension tool for leo projects.
+
   :pypi:`pytest-level`
      *last release*: Oct 21, 2019,
      *status*: N/A,
@@ -4221,14 +7200,14 @@ This list contains 963 plugins.
      Select tests of a given level or lower
 
   :pypi:`pytest-libfaketime`
-     *last release*: Dec 22, 2018,
+     *last release*: Apr 12, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.0.0)
+     *requires*: pytest>=3.0.0
 
-     A python-libfaketime plugin for pytest.
+     A python-libfaketime plugin for pytest
 
   :pypi:`pytest-libiio`
-     *last release*: Oct 29, 2021,
+     *last release*: Oct 01, 2024,
      *status*: 4 - Beta,
      *requires*: N/A
 
@@ -4256,8 +7235,15 @@ This list contains 963 plugins.
      A pytest plugin to show the line numbers of test functions
 
   :pypi:`pytest-line-profiler`
-     *last release*: May 03, 2021,
+     *last release*: Aug 10, 2023,
      *status*: 4 - Beta,
+     *requires*: pytest >=3.5.0
+
+     Profile code executed by pytest
+
+  :pypi:`pytest-line-profiler-apn`
+     *last release*: Dec 05, 2022,
+     *status*: N/A,
      *requires*: pytest (>=3.5.0)
 
      Profile code executed by pytest
@@ -4270,7 +7256,7 @@ This list contains 963 plugins.
      Pytest plugin for organizing tests.
 
   :pypi:`pytest-listener`
-     *last release*: May 28, 2019,
+     *last release*: Nov 29, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
@@ -4283,6 +7269,13 @@ This list contains 963 plugins.
 
      A pytest plugin that stream output in LITF format
 
+  :pypi:`pytest-litter`
+     *last release*: Nov 23, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest >=6.1
+
+     Pytest plugin which verifies that tests do not modify file trees.
+
   :pypi:`pytest-live`
      *last release*: Mar 08, 2020,
      *status*: N/A,
@@ -4290,38 +7283,59 @@ This list contains 963 plugins.
 
      Live results for pytest
 
+  :pypi:`pytest-llmeval`
+     *last release*: Mar 19, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A pytest plugin to evaluate/benchmark LLM prompts
+
+  :pypi:`pytest-local-badge`
+     *last release*: Jan 15, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=6.1.0)
+
+     Generate local badges (shields) reporting your test suite status.
+
   :pypi:`pytest-localftpserver`
-     *last release*: Aug 25, 2021,
+     *last release*: May 19, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      A PyTest plugin which provides an FTP fixture for your tests
 
   :pypi:`pytest-localserver`
-     *last release*: Nov 19, 2021,
+     *last release*: Oct 06, 2024,
      *status*: 4 - Beta,
      *requires*: N/A
 
-     py.test plugin to test server connections locally.
+     pytest plugin to test server connections locally.
 
   :pypi:`pytest-localstack`
-     *last release*: Aug 22, 2019,
+     *last release*: Jun 07, 2023,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.3.0)
+     *requires*: pytest (>=6.0.0,<7.0.0)
 
      Pytest plugin for AWS integration tests
 
+  :pypi:`pytest-lock`
+     *last release*: Feb 03, 2024,
+     *status*: N/A,
+     *requires*: pytest (>=7.4.3,<8.0.0)
+
+     pytest-lock is a pytest plugin that allows you to "lock" the results of unit tests, storing them in a local cache. This is particularly useful for tests that are resource-intensive or don't need to be run every time. When the tests are run subsequently, pytest-lock will compare the current results with the locked results and issue a warning if there are any discrepancies.
+
   :pypi:`pytest-lockable`
-     *last release*: Nov 09, 2021,
+     *last release*: Jan 24, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      lockable resource plugin for pytest
 
   :pypi:`pytest-locker`
-     *last release*: Oct 29, 2021,
+     *last release*: Dec 20, 2024,
      *status*: N/A,
-     *requires*: pytest (>=5.4)
+     *requires*: pytest>=5.4
 
      Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed
 
@@ -4354,8 +7368,8 @@ This list contains 963 plugins.
      Pytest plugin providing three logger fixtures with basic or full writing to log files
 
   :pypi:`pytest-logger`
-     *last release*: Jul 25, 2019,
-     *status*: 4 - Beta,
+     *last release*: Mar 10, 2024,
+     *status*: 5 - Production/Stable,
      *requires*: pytest (>=3.2)
 
      Plugin configuring handlers for loggers from Python logging module.
@@ -4367,6 +7381,27 @@ This list contains 963 plugins.
 
      Configures logging and allows tweaking the log level with a py.test flag
 
+  :pypi:`pytest-logging-end-to-end-test-tool`
+     *last release*: Sep 23, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.1.2,<8.0.0)
+
+
+
+  :pypi:`pytest-logging-strict`
+     *last release*: Mar 23, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     pytest fixture logging configured from packaged YAML
+
+  :pypi:`pytest-logikal`
+     *last release*: Apr 02, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest==8.3.5
+
+     Common testing environment
+
   :pypi:`pytest-log-report`
      *last release*: Dec 26, 2019,
      *status*: N/A,
@@ -4374,13 +7409,69 @@ This list contains 963 plugins.
 
      Package for creating a pytest test run reprot
 
+  :pypi:`pytest-logscanner`
+     *last release*: Sep 30, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.2.2
+
+     Pytest plugin for logscanner (A logger for python logging outputting to easily viewable (and filterable) html files. Good for people not grep savey, and color higlighting and quickly changing filters might even bye useful for commandline wizards.)
+
+  :pypi:`pytest-loguru`
+     *last release*: Mar 20, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest; extra == "test"
+
+     Pytest Loguru
+
+  :pypi:`pytest-loop`
+     *last release*: Oct 17, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     pytest plugin for looping tests
+
+  :pypi:`pytest-lsp`
+     *last release*: Nov 23, 2024,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     A pytest plugin for end-to-end testing of language servers
+
+  :pypi:`pytest-lw-realtime-result`
+     *last release*: Mar 13, 2025,
+     *status*: N/A,
+     *requires*: pytest>=3.5.0
+
+     Pytest plugin to generate realtime test results to a file
+
+  :pypi:`pytest-manifest`
+     *last release*: Apr 01, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     PyTest plugin for recording and asserting against a manifest file
+
   :pypi:`pytest-manual-marker`
-     *last release*: Oct 11, 2021,
+     *last release*: Aug 04, 2022,
      *status*: 3 - Alpha,
-     *requires*: pytest (>=6)
+     *requires*: pytest>=7
 
      pytest marker for marking manual tests
 
+  :pypi:`pytest-mark-count`
+     *last release*: Nov 13, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.0.0
+
+     Get a count of the number of tests marked, unmarked, and unique tests if tests have multiple markers
+
+  :pypi:`pytest-markdoctest`
+     *last release*: Jul 22, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=6)
+
+     A pytest plugin to doctest your markdown files
+
   :pypi:`pytest-markdown`
      *last release*: Jan 15, 2021,
      *status*: 4 - Beta,
@@ -4388,17 +7479,24 @@ This list contains 963 plugins.
 
      Test your markdown docs with pytest
 
-  :pypi:`pytest-marker-bugzilla`
-     *last release*: Jan 09, 2020,
+  :pypi:`pytest-markdown-docs`
+     *last release*: Mar 13, 2025,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest>=7.0.0
+
+     Run markdown code fences through pytest
+
+  :pypi:`pytest-marker-bugzilla`
+     *last release*: Apr 02, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=2.2.4
 
      py.test bugzilla integration plugin, using markers
 
   :pypi:`pytest-markers-presence`
-     *last release*: Feb 04, 2021,
+     *last release*: Oct 30, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=6.0)
+     *requires*: pytest>=6.0
 
      A simple plugin to detect missed pytest tags and markers"
 
@@ -4409,6 +7507,13 @@ This list contains 963 plugins.
 
      UNKNOWN
 
+  :pypi:`pytest-mark-manage`
+     *last release*: Aug 15, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     用例标签化管理
+
   :pypi:`pytest-mark-no-py3`
      *last release*: May 17, 2019,
      *status*: N/A,
@@ -4423,12 +7528,26 @@ This list contains 963 plugins.
 
      UNKNOWN
 
+  :pypi:`pytest-mask-secrets`
+     *last release*: Jan 28, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest plugin to hide sensitive data in test reports
+
   :pypi:`pytest-matcher`
-     *last release*: Apr 23, 2020,
+     *last release*: Aug 01, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.4)
+     *requires*: pytest
+
+     Easy way to match captured \`pytest\` output against expectations stored in files
 
-     Match test output against patterns stored in files
+  :pypi:`pytest-matchers`
+     *last release*: Feb 11, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0,>=7.0
+
+     Matchers for pytest
 
   :pypi:`pytest-match-skip`
      *last release*: May 15, 2019,
@@ -4451,6 +7570,34 @@ This list contains 963 plugins.
 
      Provide tools for generating tests from combinations of fixtures.
 
+  :pypi:`pytest-maxcov`
+     *last release*: Sep 24, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.4.0,<8.0.0)
+
+     Compute the maximum coverage available through pytest with the minimum execution time cost
+
+  :pypi:`pytest-max-warnings`
+     *last release*: Oct 23, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.3.3
+
+     A Pytest plugin to exit non-zero exit code when the configured maximum warnings has been exceeded.
+
+  :pypi:`pytest-maybe-context`
+     *last release*: Apr 16, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7,<8)
+
+     Simplify tests with warning and exception cases.
+
+  :pypi:`pytest-maybe-raises`
+     *last release*: May 27, 2022,
+     *status*: N/A,
+     *requires*: pytest ; extra == 'dev'
+
+     Pytest fixture for optional exception testing.
+
   :pypi:`pytest-mccabe`
      *last release*: Jul 22, 2020,
      *status*: 3 - Alpha,
@@ -4466,11 +7613,25 @@ This list contains 963 plugins.
      Plugin for generating Markdown reports for pytest results
 
   :pypi:`pytest-md-report`
-     *last release*: May 04, 2021,
+     *last release*: Jan 02, 2025,
      *status*: 4 - Beta,
-     *requires*: pytest (!=6.0.0,<7,>=3.3.2)
+     *requires*: pytest!=6.0.0,<9,>=3.3.2
+
+     A pytest plugin to generate test outcomes reports with markdown table format.
+
+  :pypi:`pytest-meilisearch`
+     *last release*: Oct 08, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.4.3
+
+     Pytest helpers for testing projects using Meilisearch
+
+  :pypi:`pytest-memlog`
+     *last release*: May 03, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.3.0,<8.0.0)
 
-     A pytest plugin to make a test results report with Markdown table format.
+     Log memory usage during tests
 
   :pypi:`pytest-memprof`
      *last release*: Mar 29, 2019,
@@ -4479,6 +7640,13 @@ This list contains 963 plugins.
 
      Estimates memory consumption of test functions
 
+  :pypi:`pytest-memray`
+     *last release*: Jul 25, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.2
+
+     A simple plugin to use with pytest
+
   :pypi:`pytest-menu`
      *last release*: Oct 04, 2017,
      *status*: 3 - Alpha,
@@ -4493,24 +7661,38 @@ This list contains 963 plugins.
 
      pytest plugin to write integration tests for projects using Mercurial Python internals
 
+  :pypi:`pytest-mergify`
+     *last release*: Mar 31, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest plugin for Mergify
+
+  :pypi:`pytest-mesh`
+     *last release*: Aug 05, 2022,
+     *status*: N/A,
+     *requires*: pytest (==7.1.2)
+
+     pytest_mesh插件
+
   :pypi:`pytest-message`
-     *last release*: Nov 04, 2021,
+     *last release*: Aug 04, 2022,
      *status*: N/A,
      *requires*: pytest (>=6.2.5)
 
      Pytest plugin for sending report message of marked tests execution
 
   :pypi:`pytest-messenger`
-     *last release*: Dec 16, 2020,
+     *last release*: Nov 24, 2022,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
      Pytest to Slack reporting plugin
 
   :pypi:`pytest-metadata`
-     *last release*: Nov 27, 2020,
+     *last release*: Feb 12, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=2.9.0)
+     *requires*: pytest>=7.0.0
 
      pytest plugin for test session metadata
 
@@ -4521,6 +7703,13 @@ This list contains 963 plugins.
 
      Custom metrics report for pytest
 
+  :pypi:`pytest-mh`
+     *last release*: Mar 06, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest multihost plugin
+
   :pypi:`pytest-mimesis`
      *last release*: Mar 21, 2020,
      *status*: 5 - Production/Stable,
@@ -4529,12 +7718,26 @@ This list contains 963 plugins.
      Mimesis integration with the pytest test runner
 
   :pypi:`pytest-minecraft`
-     *last release*: Sep 26, 2020,
+     *last release*: Apr 06, 2022,
      *status*: N/A,
-     *requires*: pytest (>=6.0.1,<7.0.0)
+     *requires*: pytest (>=6.0.1)
 
      A pytest plugin for running tests against Minecraft releases
 
+  :pypi:`pytest-mini`
+     *last release*: Feb 06, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.2.0,<8.0.0)
+
+     A plugin to test mp
+
+  :pypi:`pytest-minio-mock`
+     *last release*: Aug 27, 2024,
+     *status*: N/A,
+     *requires*: pytest>=5.0.0
+
+     A pytest plugin for mocking Minio S3 interactions
+
   :pypi:`pytest-missing-fixtures`
      *last release*: Oct 14, 2020,
      *status*: 4 - Beta,
@@ -4542,6 +7745,20 @@ This list contains 963 plugins.
 
      Pytest plugin that creates missing fixtures
 
+  :pypi:`pytest-missing-modules`
+     *last release*: Sep 03, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8.3.2
+
+     Pytest plugin to easily fake missing modules
+
+  :pypi:`pytest-mitmproxy`
+     *last release*: Nov 13, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.0
+
+     pytest plugin for mitmproxy tests
+
   :pypi:`pytest-ml`
      *last release*: May 04, 2019,
      *status*: 4 - Beta,
@@ -4557,9 +7774,9 @@ This list contains 963 plugins.
      pytest plugin to display test execution output like a mochajs
 
   :pypi:`pytest-mock`
-     *last release*: May 06, 2021,
+     *last release*: Mar 21, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.0)
+     *requires*: pytest>=6.2.5
 
      Thin-wrapper around the mock package for easier use with pytest
 
@@ -4571,7 +7788,7 @@ This list contains 963 plugins.
      A mock API server with configurable routes and responses available as a fixture.
 
   :pypi:`pytest-mock-generator`
-     *last release*: Aug 10, 2021,
+     *last release*: May 16, 2022,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
@@ -4599,16 +7816,16 @@ This list contains 963 plugins.
      An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database.
 
   :pypi:`pytest-mock-resources`
-     *last release*: Dec 03, 2021,
+     *last release*: Mar 10, 2025,
      *status*: N/A,
-     *requires*: pytest (>=1.0)
+     *requires*: pytest>=1.0
 
      A pytest plugin for easily instantiating reproducible mock resources.
 
   :pypi:`pytest-mock-server`
-     *last release*: Apr 06, 2020,
+     *last release*: Jan 09, 2022,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest (>=3.5.0)
 
      Mock server plugin for pytest
 
@@ -4619,6 +7836,27 @@ This list contains 963 plugins.
 
      A set of fixtures to test your requests to HTTP/UDP servers
 
+  :pypi:`pytest-mocktcp`
+     *last release*: Oct 11, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     A pytest plugin for testing TCP clients
+
+  :pypi:`pytest-modalt`
+     *last release*: Feb 27, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest >=6.2.0
+
+     Massively distributed pytest runs using modal.com
+
+  :pypi:`pytest-modified-env`
+     *last release*: Jan 29, 2022,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Pytest plugin to fail a test if it leaves modified \`os.environ\` afterwards.
+
   :pypi:`pytest-modifyjunit`
      *last release*: Jan 10, 2019,
      *status*: N/A,
@@ -4626,36 +7864,50 @@ This list contains 963 plugins.
 
      Utility for adding additional properties to junit xml for IDM QE
 
-  :pypi:`pytest-modifyscope`
-     *last release*: Apr 12, 2020,
-     *status*: N/A,
-     *requires*: pytest
+  :pypi:`pytest-molecule`
+     *last release*: Mar 29, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=7.0.0)
 
-     pytest plugin to modify fixture scope
+     PyTest Molecule Plugin :: discover and run molecule tests
 
-  :pypi:`pytest-molecule`
-     *last release*: Oct 06, 2021,
+  :pypi:`pytest-molecule-JC`
+     *last release*: Jul 18, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest (>=7.0.0)
 
      PyTest Molecule Plugin :: discover and run molecule tests
 
   :pypi:`pytest-mongo`
-     *last release*: Jun 07, 2021,
+     *last release*: Feb 28, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest
+     *requires*: pytest>=6.2
 
      MongoDB process and client fixtures plugin for Pytest.
 
   :pypi:`pytest-mongodb`
-     *last release*: Dec 07, 2019,
+     *last release*: May 16, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=2.5.2)
+     *requires*: N/A
 
      pytest plugin for MongoDB fixtures
 
+  :pypi:`pytest-mongodb-nono`
+     *last release*: Jan 07, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest plugin for MongoDB
+
+  :pypi:`pytest-mongodb-ry`
+     *last release*: Jan 21, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest plugin for MongoDB
+
   :pypi:`pytest-monitor`
-     *last release*: Aug 24, 2021,
+     *last release*: Jun 25, 2023,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
@@ -4682,6 +7934,13 @@ This list contains 963 plugins.
 
      Fixtures for integration tests of AWS services,uses moto mocking library.
 
+  :pypi:`pytest-moto-fixtures`
+     *last release*: Feb 04, 2025,
+     *status*: 1 - Planning,
+     *requires*: pytest<9,>=8.3; extra == "pytest"
+
+     Fixtures for testing code that interacts with AWS
+
   :pypi:`pytest-motor`
      *last release*: Jul 21, 2021,
      *status*: 3 - Alpha,
@@ -4697,32 +7956,39 @@ This list contains 963 plugins.
      A test batcher for multiprocessed Pytest runs
 
   :pypi:`pytest-mpi`
-     *last release*: Mar 14, 2021,
+     *last release*: Jan 08, 2022,
      *status*: 3 - Alpha,
      *requires*: pytest
 
      pytest plugin to collect information from tests
 
+  :pypi:`pytest-mpiexec`
+     *last release*: Jul 29, 2024,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     pytest plugin for running individual tests with mpiexec
+
   :pypi:`pytest-mpl`
-     *last release*: Jul 02, 2021,
+     *last release*: Feb 14, 2024,
      *status*: 4 - Beta,
      *requires*: pytest
 
      pytest plugin to help with testing figures output from Matplotlib
 
   :pypi:`pytest-mproc`
-     *last release*: Mar 07, 2021,
+     *last release*: Nov 15, 2022,
      *status*: 4 - Beta,
-     *requires*: pytest
+     *requires*: pytest (>=6)
 
      low-startup-overhead, scalable, distributed-testing pytest plugin
 
-  :pypi:`pytest-multi-check`
-     *last release*: Jun 03, 2021,
-     *status*: N/A,
-     *requires*: pytest
+  :pypi:`pytest-mqtt`
+     *last release*: Jan 07, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9; extra == "test"
 
-     Pytest-плагин, реализует возможность мульти проверок и мягких проверок
+     pytest-mqtt supports testing systems based on MQTT
 
   :pypi:`pytest-multihost`
      *last release*: Apr 07, 2020,
@@ -4732,19 +7998,26 @@ This list contains 963 plugins.
      Utility for writing multi-host tests for pytest
 
   :pypi:`pytest-multilog`
-     *last release*: Jun 10, 2021,
+     *last release*: Jan 17, 2023,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest
 
      Multi-process logs handling and other helpers for pytest
 
   :pypi:`pytest-multithreading`
-     *last release*: Aug 12, 2021,
+     *last release*: Aug 05, 2024,
      *status*: N/A,
-     *requires*: pytest (>=3.6)
+     *requires*: N/A
 
      a pytest plugin for th and concurrent testing
 
+  :pypi:`pytest-multithreading-allure`
+     *last release*: Nov 25, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest_multithreading_allure
+
   :pypi:`pytest-mutagen`
      *last release*: Jul 24, 2020,
      *status*: N/A,
@@ -4752,12 +8025,26 @@ This list contains 963 plugins.
 
      Add the mutation testing feature to pytest
 
+  :pypi:`pytest-my-cool-lib`
+     *last release*: Nov 02, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.1.3,<8.0.0)
+
+
+
+  :pypi:`pytest-my-plugin`
+     *last release*: Jan 27, 2025,
+     *status*: N/A,
+     *requires*: pytest>=6.0
+
+     A pytest plugin that does awesome things
+
   :pypi:`pytest-mypy`
-     *last release*: Mar 21, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=3.5)
+     *last release*: Apr 02, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.0
 
-     Mypy static type checker plugin for Pytest
+     A Pytest Plugin for Mypy
 
   :pypi:`pytest-mypyd`
      *last release*: Aug 20, 2019,
@@ -4767,46 +8054,81 @@ This list contains 963 plugins.
      Mypy static type checker plugin for Pytest
 
   :pypi:`pytest-mypy-plugins`
-     *last release*: Oct 19, 2021,
-     *status*: 3 - Alpha,
-     *requires*: pytest (>=6.0.0)
+     *last release*: Dec 21, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.0.0
 
      pytest plugin for writing tests for mypy plugins
 
   :pypi:`pytest-mypy-plugins-shim`
-     *last release*: Apr 12, 2021,
+     *last release*: Feb 14, 2025,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest>=6.0.0
 
      Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy.
 
+  :pypi:`pytest-mypy-runner`
+     *last release*: Apr 23, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8.0
+
+     Run the mypy static type checker as a pytest test case
+
   :pypi:`pytest-mypy-testing`
-     *last release*: Jun 13, 2021,
+     *last release*: Mar 04, 2024,
      *status*: N/A,
-     *requires*: pytest
+     *requires*: pytest>=7,<9
 
      Pytest plugin to check mypy output.
 
   :pypi:`pytest-mysql`
-     *last release*: Nov 22, 2021,
+     *last release*: Dec 10, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest
+     *requires*: pytest>=6.2
 
      MySQL process and client fixtures for pytest
 
+  :pypi:`pytest-ndb`
+     *last release*: Apr 28, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     pytest notebook debugger
+
   :pypi:`pytest-needle`
      *last release*: Dec 10, 2018,
      *status*: 4 - Beta,
      *requires*: pytest (<5.0.0,>=3.0.0)
 
-     pytest plugin for visual testing websites using selenium
+     pytest plugin for visual testing websites using selenium
+
+  :pypi:`pytest-neo`
+     *last release*: Jan 08, 2022,
+     *status*: 3 - Alpha,
+     *requires*: pytest (>=6.2.0)
+
+     pytest-neo is a plugin for pytest that shows tests like screen of Matrix.
+
+  :pypi:`pytest-neos`
+     *last release*: Sep 10, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<8.0,>=7.2; extra == "dev"
+
+     Pytest plugin for neos
 
-  :pypi:`pytest-neo`
-     *last release*: Apr 23, 2019,
-     *status*: 3 - Alpha,
-     *requires*: pytest (>=3.7.2)
+  :pypi:`pytest-netconf`
+     *last release*: Jan 06, 2025,
+     *status*: N/A,
+     *requires*: N/A
 
-     pytest-neo is a plugin for pytest that shows tests like screen of Matrix.
+     A pytest plugin that provides a mock NETCONF (RFC6241/RFC6242) server for local testing.
+
+  :pypi:`pytest-netdut`
+     *last release*: Jul 05, 2024,
+     *status*: N/A,
+     *requires*: pytest<7.3,>=3.5.0
+
+     "Automated software testing for switches using pytest"
 
   :pypi:`pytest-network`
      *last release*: May 07, 2020,
@@ -4815,6 +8137,13 @@ This list contains 963 plugins.
 
      A simple plugin to disable network on socket level.
 
+  :pypi:`pytest-network-endpoints`
+     *last release*: Mar 06, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     Network endpoints plugin for pytest
+
   :pypi:`pytest-never-sleep`
      *last release*: May 05, 2021,
      *status*: 3 - Alpha,
@@ -4837,9 +8166,9 @@ This list contains 963 plugins.
      nginx fixture for pytest - iplweb temporary fork
 
   :pypi:`pytest-ngrok`
-     *last release*: Jan 22, 2020,
+     *last release*: Jan 20, 2022,
      *status*: 3 - Alpha,
-     *requires*: N/A
+     *requires*: pytest
 
 
 
@@ -4850,6 +8179,13 @@ This list contains 963 plugins.
 
      pytest ngs fixtures
 
+  :pypi:`pytest-nhsd-apim`
+     *last release*: Apr 01, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.2.0
+
+     Pytest plugin accessing NHSDigital's APIM proxies
+
   :pypi:`pytest-nice`
      *last release*: May 04, 2019,
      *status*: 4 - Beta,
@@ -4864,20 +8200,27 @@ This list contains 963 plugins.
 
      A small snippet for nicer PyTest's Parametrize
 
-  :pypi:`pytest-nlcov`
-     *last release*: Jul 07, 2021,
+  :pypi:`pytest_nlcov`
+     *last release*: Aug 05, 2024,
      *status*: N/A,
      *requires*: N/A
 
      Pytest plugin to get the coverage of the new lines (based on git diff) only
 
   :pypi:`pytest-nocustom`
-     *last release*: Jul 07, 2021,
+     *last release*: Aug 05, 2024,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
      Run all tests without custom markers
 
+  :pypi:`pytest-node-dependency`
+     *last release*: Apr 10, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     pytest plugin for controlling execution flow
+
   :pypi:`pytest-nodev`
      *last release*: Jul 21, 2016,
      *status*: 4 - Beta,
@@ -4886,18 +8229,25 @@ This list contains 963 plugins.
      Test-driven source code search for Python.
 
   :pypi:`pytest-nogarbage`
-     *last release*: Aug 29, 2021,
+     *last release*: Feb 24, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=4.6.0)
+     *requires*: pytest>=4.6.0
 
      Ensure a test produces no garbage
 
-  :pypi:`pytest-notebook`
-     *last release*: Sep 16, 2020,
+  :pypi:`pytest-nose-attrib`
+     *last release*: Aug 13, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest plugin to use nose @attrib marks decorators and pick tests based on attributes and partially uses nose-attrib plugin approach
+
+  :pypi:`pytest_notebook`
+     *last release*: Nov 28, 2023,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest>=3.5.0
 
-     A pytest plugin for testing Jupyter Notebooks
+     A pytest plugin for testing Jupyter Notebooks.
 
   :pypi:`pytest-notice`
      *last release*: Nov 05, 2020,
@@ -4920,6 +8270,13 @@ This list contains 963 plugins.
 
      A pytest plugin to notify test result
 
+  :pypi:`pytest_notify`
+     *last release*: Jul 05, 2017,
+     *status*: N/A,
+     *requires*: pytest>=3.0.0
+
+     Get notifications when your tests ends
+
   :pypi:`pytest-notimplemented`
      *last release*: Aug 27, 2019,
      *status*: N/A,
@@ -4935,12 +8292,33 @@ This list contains 963 plugins.
      A PyTest Reporter to send test runs to Notion.so
 
   :pypi:`pytest-nunit`
-     *last release*: Aug 04, 2020,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+     *last release*: Feb 26, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
 
      A pytest plugin for generating NUnit3 test result XML output
 
+  :pypi:`pytest-oar`
+     *last release*: May 02, 2023,
+     *status*: N/A,
+     *requires*: pytest>=6.0.1
+
+     PyTest plugin for the OAR testing framework
+
+  :pypi:`pytest-oarepo`
+     *last release*: Feb 14, 2025,
+     *status*: N/A,
+     *requires*: pytest>=7.1.2; extra == "base"
+
+
+
+  :pypi:`pytest-object-getter`
+     *last release*: Jul 31, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Import any object from a 3rd party module while mocking its namespace on demand.
+
   :pypi:`pytest-ochrus`
      *last release*: Feb 21, 2018,
      *status*: 4 - Beta,
@@ -4948,10 +8326,17 @@ This list contains 963 plugins.
 
      pytest results data-base and HTML reporter
 
-  :pypi:`pytest-odoo`
-     *last release*: Nov 04, 2021,
+  :pypi:`pytest-odc`
+     *last release*: Aug 04, 2023,
      *status*: 4 - Beta,
-     *requires*: pytest (>=2.9)
+     *requires*: pytest (>=3.5.0)
+
+     A pytest plugin for simplifying ODC database tests
+
+  :pypi:`pytest-odoo`
+     *last release*: Mar 12, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=8
 
      py.test plugin to run Odoo tests
 
@@ -4969,6 +8354,20 @@ This list contains 963 plugins.
 
      pytest plugin to test OpenERP modules
 
+  :pypi:`pytest-offline`
+     *last release*: Mar 09, 2023,
+     *status*: 1 - Planning,
+     *requires*: pytest (>=7.0.0,<8.0.0)
+
+
+
+  :pypi:`pytest-ogsm-plugin`
+     *last release*: May 16, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数
+
   :pypi:`pytest-ok`
      *last release*: Apr 01, 2019,
      *status*: 4 - Beta,
@@ -4977,12 +8376,19 @@ This list contains 963 plugins.
      The ultimate pytest output plugin
 
   :pypi:`pytest-only`
-     *last release*: Jan 19, 2020,
-     *status*: N/A,
-     *requires*: N/A
+     *last release*: May 27, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9,>=3.6.0
 
      Use @pytest.mark.only to run a single test
 
+  :pypi:`pytest-oof`
+     *last release*: Dec 11, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     A Pytest plugin providing structured, programmatic access to a test run's results
+
   :pypi:`pytest-oot`
      *last release*: Sep 18, 2016,
      *status*: 4 - Beta,
@@ -4991,23 +8397,37 @@ This list contains 963 plugins.
      Run object-oriented tests in a simple format
 
   :pypi:`pytest-openfiles`
-     *last release*: Apr 16, 2020,
+     *last release*: Jun 05, 2024,
      *status*: 3 - Alpha,
-     *requires*: pytest (>=4.6)
+     *requires*: pytest>=4.6
 
      Pytest plugin for detecting inadvertent open file handles
 
+  :pypi:`pytest-open-html`
+     *last release*: Mar 31, 2025,
+     *status*: N/A,
+     *requires*: pytest>=6.0
+
+     Auto-open HTML reports after pytest runs
+
+  :pypi:`pytest-opentelemetry`
+     *last release*: Nov 22, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     A pytest plugin for instrumenting test runs via OpenTelemetry
+
   :pypi:`pytest-opentmi`
-     *last release*: Nov 04, 2021,
+     *last release*: Mar 22, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.0)
+     *requires*: pytest>=5.0
 
      pytest plugin for publish results to opentmi
 
   :pypi:`pytest-operator`
-     *last release*: Oct 26, 2021,
+     *last release*: Sep 28, 2022,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest
 
      Fixtures for Operators
 
@@ -5033,12 +8453,19 @@ This list contains 963 plugins.
      A pytest plugin for orchestrating tests
 
   :pypi:`pytest-order`
-     *last release*: May 30, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=5.0)
+     *last release*: Aug 22, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=5.0; python_version < "3.10"
 
      pytest plugin to run your tests in a specific order
 
+  :pypi:`pytest-ordered`
+     *last release*: Oct 07, 2024,
+     *status*: N/A,
+     *requires*: pytest>=6.2.0
+
+     Declare the order in which tests should run in your pytest.ini
+
   :pypi:`pytest-ordering`
      *last release*: Nov 14, 2018,
      *status*: 4 - Beta,
@@ -5046,6 +8473,13 @@ This list contains 963 plugins.
 
      pytest plugin to run your tests in a specific order
 
+  :pypi:`pytest-order-modify`
+     *last release*: Nov 04, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     新增run_marker 来自定义用例的执行顺序
+
   :pypi:`pytest-osxnotify`
      *last release*: May 15, 2015,
      *status*: N/A,
@@ -5053,12 +8487,33 @@ This list contains 963 plugins.
 
      OS X notifications for py.test results.
 
+  :pypi:`pytest-ot`
+     *last release*: Mar 21, 2024,
+     *status*: N/A,
+     *requires*: pytest; extra == "dev"
+
+     A pytest plugin for instrumenting test runs via OpenTelemetry
+
   :pypi:`pytest-otel`
-     *last release*: Dec 03, 2021,
+     *last release*: Feb 10, 2025,
+     *status*: N/A,
+     *requires*: pytest==8.3.4
+
+     OpenTelemetry plugin for Pytest
+
+  :pypi:`pytest-override-env-var`
+     *last release*: Feb 25, 2023,
      *status*: N/A,
      *requires*: N/A
 
-     pytest-otel report OpenTelemetry traces about test executed
+     Pytest mark to override a value of an environment variable.
+
+  :pypi:`pytest-owner`
+     *last release*: Aug 19, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Add owner mark for tests
 
   :pypi:`pytest-pact`
      *last release*: Jan 07, 2019,
@@ -5067,6 +8522,13 @@ This list contains 963 plugins.
 
      A simple plugin to use with pytest
 
+  :pypi:`pytest-pagerduty`
+     *last release*: Mar 22, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=7.4.0
+
+     Pytest plugin for PagerDuty integration via automation testing.
+
   :pypi:`pytest-pahrametahrize`
      *last release*: Nov 24, 2021,
      *status*: 4 - Beta,
@@ -5088,6 +8550,13 @@ This list contains 963 plugins.
 
      a pytest plugin for parallel and concurrent testing
 
+  :pypi:`pytest-parallelize-tests`
+     *last release*: Jan 27, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     pytest plugin that parallelizes test execution across multiple hosts
+
   :pypi:`pytest-param`
      *last release*: Sep 11, 2016,
      *status*: 4 - Beta,
@@ -5103,25 +8572,67 @@ This list contains 963 plugins.
      Configure pytest fixtures using a combination of"parametrize" and markers
 
   :pypi:`pytest-parametrization`
-     *last release*: Nov 30, 2021,
+     *last release*: May 22, 2022,
      *status*: 5 - Production/Stable,
-     *requires*: pytest
+     *requires*: N/A
 
      Simpler PyTest parametrization
 
+  :pypi:`pytest-parametrization-annotation`
+     *last release*: Dec 10, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7
+
+     A pytest library for parametrizing tests using type hints.
+
+  :pypi:`pytest-parametrize`
+     *last release*: Nov 10, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9.0.0,>=8.3.0
+
+     pytest decorator for parametrizing test cases in a dict-way
+
   :pypi:`pytest-parametrize-cases`
-     *last release*: Dec 12, 2020,
+     *last release*: Mar 13, 2022,
      *status*: N/A,
-     *requires*: pytest (>=6.1.2,<7.0.0)
+     *requires*: pytest (>=6.1.2)
 
      A more user-friendly way to write parametrized tests.
 
   :pypi:`pytest-parametrized`
-     *last release*: Oct 19, 2020,
+     *last release*: Dec 21, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
-     Pytest plugin for parametrizing tests with default iterables.
+     Pytest decorator for parametrizing tests with default iterables.
+
+  :pypi:`pytest-parametrize-suite`
+     *last release*: Jan 19, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     A simple pytest extension for creating a named test suite.
+
+  :pypi:`pytest_param_files`
+     *last release*: Jul 29, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     Create pytest parametrize decorators from external files.
+
+  :pypi:`pytest-params`
+     *last release*: Aug 05, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
+
+     Simplified pytest test case parameters.
+
+  :pypi:`pytest-param-scope`
+     *last release*: Oct 18, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     pytest parametrize scope fixture workaround
 
   :pypi:`pytest-parawtf`
      *last release*: Dec 03, 2018,
@@ -5151,6 +8662,13 @@ This list contains 963 plugins.
 
      Allow setting the path to a paste config file
 
+  :pypi:`pytest-patch`
+     *last release*: Apr 29, 2023,
+     *status*: 3 - Alpha,
+     *requires*: pytest (>=7.0.0)
+
+     An automagic \`patch\` fixture that can patch objects directly or by name.
+
   :pypi:`pytest-patches`
      *last release*: Aug 30, 2021,
      *status*: 4 - Beta,
@@ -5158,6 +8676,13 @@ This list contains 963 plugins.
 
      A contextmanager pytest fixture for handling multiple mock patches
 
+  :pypi:`pytest-patterns`
+     *last release*: Oct 22, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6
+
+     pytest plugin to make testing complicated long string output easy to write and easy to debug
+
   :pypi:`pytest-pdb`
      *last release*: Jul 31, 2018,
      *status*: N/A,
@@ -5193,12 +8718,19 @@ This list contains 963 plugins.
 
      Change the exit code of pytest test sessions when a required percent of tests pass.
 
+  :pypi:`pytest-percents`
+     *last release*: Mar 16, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
   :pypi:`pytest-perf`
-     *last release*: Jun 27, 2021,
+     *last release*: May 20, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=4.6) ; extra == 'testing'
+     *requires*: pytest!=8.1.*,>=6; extra == "testing"
 
-     pytest-perf
+     Run performance tests against the mainline code.
 
   :pypi:`pytest-performance`
      *last release*: Sep 11, 2020,
@@ -5207,13 +8739,34 @@ This list contains 963 plugins.
 
      A simple plugin to ensure the execution of critical sections of code has not been impacted
 
+  :pypi:`pytest-performancetotal`
+     *last release*: Feb 01, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     A performance plugin for pytest
+
   :pypi:`pytest-persistence`
-     *last release*: Nov 06, 2021,
+     *last release*: Aug 21, 2024,
      *status*: N/A,
      *requires*: N/A
 
      Pytest tool for persistent objects
 
+  :pypi:`pytest-pexpect`
+     *last release*: Aug 13, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     Pytest pexpect plugin.
+
+  :pypi:`pytest-pg`
+     *last release*: Apr 03, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=8.0.0
+
+     A tiny plugin for pytest which runs PostgreSQL in Docker
+
   :pypi:`pytest-pgsql`
      *last release*: May 13, 2020,
      *status*: 5 - Production/Stable,
@@ -5222,19 +8775,33 @@ This list contains 963 plugins.
      Pytest plugins and helpers for tests using a Postgres database.
 
   :pypi:`pytest-phmdoctest`
-     *last release*: Nov 10, 2021,
+     *last release*: Apr 15, 2022,
      *status*: 4 - Beta,
-     *requires*: pytest (>=6.2) ; extra == 'test'
+     *requires*: pytest (>=5.4.3)
 
      pytest plugin to test Python examples in Markdown using phmdoctest.
 
+  :pypi:`pytest-phoenix-interface`
+     *last release*: Mar 19, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest extension tool for phoenix projects.
+
   :pypi:`pytest-picked`
-     *last release*: Dec 23, 2020,
+     *last release*: Nov 06, 2024,
      *status*: N/A,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest>=3.7.0
 
      Run the tests related to the changed files
 
+  :pypi:`pytest-pickle-cache`
+     *last release*: Feb 17, 2025,
+     *status*: N/A,
+     *requires*: pytest>=7
+
+     A pytest plugin for caching test results using pickle.
+
   :pypi:`pytest-pigeonhole`
      *last release*: Jun 25, 2018,
      *status*: 5 - Production/Stable,
@@ -5256,6 +8823,13 @@ This list contains 963 plugins.
 
      Slice in your test base thanks to powerful markers.
 
+  :pypi:`pytest-pingguo-pytest-plugin`
+     *last release*: Oct 26, 2022,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     pingguo test
+
   :pypi:`pytest-pings`
      *last release*: Jun 29, 2019,
      *status*: 3 - Alpha,
@@ -5284,6 +8858,20 @@ This list contains 963 plugins.
 
      Pytest plugin for functional testing of data analysispipelines
 
+  :pypi:`pytest-pitch`
+     *last release*: Nov 02, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest >=7.3.1
+
+     runs tests in an order such that coverage increases as fast as possible
+
+  :pypi:`pytest-platform-adapter`
+     *last release*: Feb 18, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=6.2.5
+
+     Pytest集成自动化平台插件
+
   :pypi:`pytest-platform-markers`
      *last release*: Sep 09, 2019,
      *status*: 4 - Beta,
@@ -5306,12 +8894,40 @@ This list contains 963 plugins.
      Pytest plugin for reading playbooks.
 
   :pypi:`pytest-playwright`
-     *last release*: Oct 28, 2021,
+     *last release*: Jan 31, 2025,
      *status*: N/A,
-     *requires*: pytest
+     *requires*: pytest<9.0.0,>=6.2.4
 
      A pytest wrapper with fixtures for Playwright to automate web browsers
 
+  :pypi:`pytest_playwright_async`
+     *last release*: Sep 28, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     ASYNC Pytest plugin for Playwright
+
+  :pypi:`pytest-playwright-asyncio`
+     *last release*: Jan 31, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=6.2.4
+
+     A pytest wrapper with async fixtures for Playwright to automate web browsers
+
+  :pypi:`pytest-playwright-axe`
+     *last release*: Mar 27, 2025,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     An axe-core integration for accessibility testing using Playwright Python.
+
+  :pypi:`pytest-playwright-enhanced`
+     *last release*: Mar 24, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.0.0
+
+     A pytest plugin for playwright python
+
   :pypi:`pytest-playwrights`
      *last release*: Dec 02, 2021,
      *status*: N/A,
@@ -5326,8 +8942,29 @@ This list contains 963 plugins.
 
      A pytest wrapper for snapshot testing with playwright
 
+  :pypi:`pytest-playwright-visual`
+     *last release*: Apr 28, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     A pytest fixture for visual testing with Playwright
+
+  :pypi:`pytest-playwright-visual-snapshot`
+     *last release*: Mar 25, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     Easy pytest visual regression testing using playwright
+
+  :pypi:`pytest-plone`
+     *last release*: Mar 27, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest<8.0.0
+
+     Pytest plugin to test Plone addons
+
   :pypi:`pytest-plt`
-     *last release*: Aug 17, 2020,
+     *last release*: Jan 17, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
@@ -5341,9 +8978,9 @@ This list contains 963 plugins.
      A plugin to help developing and testing other plugins
 
   :pypi:`pytest-plus`
-     *last release*: Mar 19, 2020,
+     *last release*: Feb 02, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.50)
+     *requires*: pytest>=7.4.2
 
      PyTest Plus Plugin :: extends pytest functionality
 
@@ -5354,13 +8991,27 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-pogo`
+     *last release*: Sep 09, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest<9,>=7
+
+     Pytest plugin for pogo-migrate
+
   :pypi:`pytest-pointers`
-     *last release*: Oct 14, 2021,
+     *last release*: Dec 26, 2022,
      *status*: N/A,
      *requires*: N/A
 
      Pytest plugin to define functions you test with special marks for better navigation and reports
 
+  :pypi:`pytest-pokie`
+     *last release*: Oct 19, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Pokie plugin for pytest
+
   :pypi:`pytest-polarion-cfme`
      *last release*: Nov 13, 2017,
      *status*: 3 - Alpha,
@@ -5403,13 +9054,27 @@ This list contains 963 plugins.
 
      Visualize your failed tests with poo
 
+  :pypi:`pytest-pook`
+     *last release*: Feb 15, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Pytest plugin for pook
+
   :pypi:`pytest-pop`
-     *last release*: Aug 19, 2021,
+     *last release*: May 09, 2023,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      A pytest plugin to help with testing pop projects
 
+  :pypi:`pytest-porcochu`
+     *last release*: Nov 28, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Show surprise when tests are passing
+
   :pypi:`pytest-portion`
      *last release*: Jan 28, 2021,
      *status*: 4 - Beta,
@@ -5425,9 +9090,9 @@ This list contains 963 plugins.
      Run PostgreSQL in Docker container in Pytest.
 
   :pypi:`pytest-postgresql`
-     *last release*: Nov 05, 2021,
+     *last release*: Mar 19, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.0.0)
+     *requires*: pytest>=6.2
 
      Postgresql fixtures and fixture factories for Pytest.
 
@@ -5438,8 +9103,29 @@ This list contains 963 plugins.
 
      pytest plugin with powerful fixtures
 
+  :pypi:`pytest-powerpack`
+     *last release*: Jan 04, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.1.1
+
+     A plugin containing extra batteries for pytest
+
+  :pypi:`pytest-prefer-nested-dup-tests`
+     *last release*: Apr 27, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=7.1.1,<8.0.0)
+
+     A Pytest plugin to drop duplicated tests during collection, but will prefer keeping nested packages.
+
+  :pypi:`pytest-pretty`
+     *last release*: Apr 05, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7
+
+     pytest plugin for printing summary data as I want it
+
   :pypi:`pytest-pretty-terminal`
-     *last release*: Nov 24, 2021,
+     *last release*: Jan 31, 2022,
      *status*: N/A,
      *requires*: pytest (>=3.4.1)
 
@@ -5453,23 +9139,44 @@ This list contains 963 plugins.
      Minitest-style test colors
 
   :pypi:`pytest-print`
-     *last release*: Jun 17, 2021,
+     *last release*: Feb 25, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=6)
+     *requires*: pytest>=8.3.2
 
      pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout)
 
+  :pypi:`pytest-priority`
+     *last release*: Aug 19, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     pytest plugin for add priority for tests
+
+  :pypi:`pytest-proceed`
+     *last release*: Oct 01, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+
+
+  :pypi:`pytest-profiles`
+     *last release*: Dec 09, 2021,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.7.0)
+
+     pytest plugin for configuration profiles
+
   :pypi:`pytest-profiling`
-     *last release*: May 28, 2019,
+     *last release*: Nov 29, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      Profiling plugin for py.test
 
   :pypi:`pytest-progress`
-     *last release*: Nov 09, 2021,
+     *last release*: Jun 18, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=2.7)
+     *requires*: pytest>=2.7
 
      pytest plugin for instant test progress status
 
@@ -5480,12 +9187,26 @@ This list contains 963 plugins.
 
      Report test pass / failures to a Prometheus PushGateway
 
+  :pypi:`pytest-prometheus-pushgateway`
+     *last release*: Sep 27, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Pytest report plugin for Zulip
+
   :pypi:`pytest-prosper`
      *last release*: Sep 24, 2018,
      *status*: N/A,
      *requires*: N/A
 
-     Test helpers for Prosper projects
+     Test helpers for Prosper projects
+
+  :pypi:`pytest-prysk`
+     *last release*: Dec 10, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.3.2
+
+     Pytest plugin for prysk
 
   :pypi:`pytest-pspec`
      *last release*: Jun 02, 2020,
@@ -5501,13 +9222,27 @@ This list contains 963 plugins.
 
      pytest plugin for testing applications that use psqlgraph
 
+  :pypi:`pytest-pt`
+     *last release*: Sep 22, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     pytest plugin to use \*.pt files as tests
+
   :pypi:`pytest-ptera`
-     *last release*: Oct 20, 2021,
+     *last release*: Mar 01, 2022,
      *status*: N/A,
      *requires*: pytest (>=6.2.4,<7.0.0)
 
      Use ptera probes in tests
 
+  :pypi:`pytest-publish`
+     *last release*: Jun 04, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.0.0
+
+
+
   :pypi:`pytest-pudb`
      *last release*: Oct 25, 2018,
      *status*: 3 - Alpha,
@@ -5515,6 +9250,13 @@ This list contains 963 plugins.
 
      Pytest PuDB debugger integration
 
+  :pypi:`pytest-pumpkin-spice`
+     *last release*: Sep 18, 2022,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     A pytest plugin that makes your test reporting pumpkin-spiced
+
   :pypi:`pytest-purkinje`
      *last release*: Oct 28, 2017,
      *status*: 2 - Pre-Alpha,
@@ -5522,6 +9264,20 @@ This list contains 963 plugins.
 
      py.test plugin for purkinje test runner
 
+  :pypi:`pytest-pusher`
+     *last release*: Jan 06, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=3.6)
+
+     pytest plugin for push report to minio
+
+  :pypi:`pytest-py125`
+     *last release*: Dec 03, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
   :pypi:`pytest-pycharm`
      *last release*: Aug 13, 2020,
      *status*: 5 - Production/Stable,
@@ -5530,12 +9286,19 @@ This list contains 963 plugins.
      Plugin for py.test to enter PyCharm debugger on uncaught exceptions
 
   :pypi:`pytest-pycodestyle`
-     *last release*: Aug 10, 2020,
+     *last release*: Oct 10, 2024,
      *status*: 3 - Alpha,
-     *requires*: N/A
+     *requires*: pytest>=7.0
 
      pytest plugin to run pycodestyle
 
+  :pypi:`pytest-pydantic-schema-sync`
+     *last release*: Aug 29, 2024,
+     *status*: N/A,
+     *requires*: pytest>=6
+
+     Pytest plugin to synchronise Pydantic model schemas with JSONSchema files
+
   :pypi:`pytest-pydev`
      *last release*: Nov 15, 2017,
      *status*: 3 - Alpha,
@@ -5544,19 +9307,40 @@ This list contains 963 plugins.
      py.test plugin to connect to a remote debug server with PyDev or PyCharm.
 
   :pypi:`pytest-pydocstyle`
-     *last release*: Aug 10, 2020,
+     *last release*: Oct 09, 2024,
      *status*: 3 - Alpha,
-     *requires*: N/A
+     *requires*: pytest>=7.0
 
      pytest plugin to run pydocstyle
 
   :pypi:`pytest-pylint`
-     *last release*: Nov 09, 2020,
+     *last release*: Oct 06, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.4)
+     *requires*: pytest >=7.0
 
      pytest plugin to check source code with pylint
 
+  :pypi:`pytest-pylyzer`
+     *last release*: Feb 15, 2025,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     A pytest plugin for pylyzer
+
+  :pypi:`pytest-pymysql-autorecord`
+     *last release*: Sep 02, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Record PyMySQL queries and mock with the stored data.
+
+  :pypi:`pytest-pyodide`
+     *last release*: Nov 23, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Pytest plugin for testing applications that use Pyodide
+
   :pypi:`pytest-pypi`
      *last release*: Mar 04, 2018,
      *status*: 3 - Alpha,
@@ -5572,11 +9356,11 @@ This list contains 963 plugins.
      Core engine for cookiecutter-qa and pytest-play packages
 
   :pypi:`pytest-pyppeteer`
-     *last release*: Feb 16, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=6.0.2)
+     *last release*: Apr 28, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=6.2.5,<7.0.0)
 
-     A plugin to run pyppeteer in pytest.
+     A plugin to run pyppeteer in pytest
 
   :pypi:`pytest-pyq`
      *last release*: Mar 10, 2020,
@@ -5586,26 +9370,47 @@ This list contains 963 plugins.
      Pytest fixture "q" for pyq
 
   :pypi:`pytest-pyramid`
-     *last release*: Oct 15, 2021,
+     *last release*: Oct 24, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite
 
   :pypi:`pytest-pyramid-server`
-     *last release*: May 28, 2019,
+     *last release*: Oct 17, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      Pyramid server fixture for py.test
 
+  :pypi:`pytest-pyreport`
+     *last release*: May 05, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     PyReport is a lightweight reporting plugin for Pytest that provides concise HTML report
+
   :pypi:`pytest-pyright`
-     *last release*: Aug 16, 2021,
+     *last release*: Jan 26, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest >=7.0.0
 
      Pytest plugin for type checking code with Pyright
 
+  :pypi:`pytest-pyspec`
+     *last release*: Aug 17, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.3.2
+
+     A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it".
+
+  :pypi:`pytest-pystack`
+     *last release*: Nov 16, 2024,
+     *status*: N/A,
+     *requires*: pytest>=3.5.0
+
+     Plugin to run pystack after a timeout for a test suite.
+
   :pypi:`pytest-pytestrail`
      *last release*: Aug 27, 2020,
      *status*: 4 - Beta,
@@ -5613,13 +9418,27 @@ This list contains 963 plugins.
 
      Pytest plugin for interaction with TestRail
 
+  :pypi:`pytest-pythonhashseed`
+     *last release*: Feb 25, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=3.0.0
+
+     Pytest plugin to set PYTHONHASHSEED env var.
+
   :pypi:`pytest-pythonpath`
-     *last release*: Aug 22, 2018,
+     *last release*: Feb 10, 2022,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest (<7,>=2.5.2)
 
      pytest plugin for adding to the PYTHONPATH from command line or configs.
 
+  :pypi:`pytest-python-test-engineer-sort`
+     *last release*: May 13, 2024,
+     *status*: N/A,
+     *requires*: pytest>=6.2.0
+
+     Sort plugin for Pytest
+
   :pypi:`pytest-pytorch`
      *last release*: May 25, 2021,
      *status*: 4 - Beta,
@@ -5627,6 +9446,34 @@ This list contains 963 plugins.
 
      pytest plugin for a better developer experience when working with the PyTorch test suite
 
+  :pypi:`pytest-pyvenv`
+     *last release*: Feb 27, 2024,
+     *status*: N/A,
+     *requires*: pytest ; extra == 'test'
+
+     A package for create venv in tests
+
+  :pypi:`pytest-pyvista`
+     *last release*: Sep 29, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest>=3.5.0
+
+     Pytest-pyvista package
+
+  :pypi:`pytest-qanova`
+     *last release*: Sep 05, 2024,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     A pytest plugin to collect test information
+
+  :pypi:`pytest-qaseio`
+     *last release*: Mar 18, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9.0.0,>=7.2.2
+
+     Pytest plugin for Qase.io integration
+
   :pypi:`pytest-qasync`
      *last release*: Jul 12, 2021,
      *status*: 4 - Beta,
@@ -5635,16 +9482,16 @@ This list contains 963 plugins.
      Pytest support for qasync.
 
   :pypi:`pytest-qatouch`
-     *last release*: Jun 26, 2021,
+     *last release*: Feb 14, 2023,
      *status*: 4 - Beta,
      *requires*: pytest (>=6.2.0)
 
      Pytest plugin for uploading test results to your QA Touch Testrun.
 
   :pypi:`pytest-qgis`
-     *last release*: Nov 25, 2021,
+     *last release*: Jun 14, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=6.2.3)
+     *requires*: pytest>=6.0
 
      A pytest plugin for testing QGIS python plugins
 
@@ -5663,16 +9510,16 @@ This list contains 963 plugins.
      pytest plugin to generate test result QR codes
 
   :pypi:`pytest-qt`
-     *last release*: Jun 13, 2021,
+     *last release*: Feb 07, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.0.0)
+     *requires*: pytest
 
      pytest support for PyQt and PySide applications
 
   :pypi:`pytest-qt-app`
-     *last release*: Dec 23, 2015,
+     *last release*: Oct 17, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest
 
      QT app fixture for py.test
 
@@ -5684,21 +9531,28 @@ This list contains 963 plugins.
      A plugin for pytest to manage expected test failures
 
   :pypi:`pytest-quickcheck`
-     *last release*: Nov 15, 2020,
+     *last release*: Nov 05, 2022,
      *status*: 4 - Beta,
-     *requires*: pytest (<6.0.0,>=4.0)
+     *requires*: pytest (>=4.0)
 
      pytest plugin to generate random data inspired by QuickCheck
 
+  :pypi:`pytest_quickify`
+     *last release*: Jun 14, 2019,
+     *status*: N/A,
+     *requires*: pytest
+
+     Run test suites with pytest-quickify.
+
   :pypi:`pytest-rabbitmq`
-     *last release*: Jun 02, 2021,
+     *last release*: Oct 15, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.0.0)
+     *requires*: pytest>=6.2
 
      RabbitMQ process and client fixtures for pytest
 
   :pypi:`pytest-race`
-     *last release*: Nov 21, 2016,
+     *last release*: Jun 07, 2022,
      *status*: 4 - Beta,
      *requires*: N/A
 
@@ -5711,8 +9565,15 @@ This list contains 963 plugins.
 
      pytest plugin to implement PEP712
 
+  :pypi:`pytest-rail`
+     *last release*: May 02, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=3.6)
+
+     pytest plugin for creating TestRail runs and adding results
+
   :pypi:`pytest-railflow-testrail-reporter`
-     *last release*: Dec 02, 2021,
+     *last release*: Jun 29, 2022,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
@@ -5733,7 +9594,7 @@ This list contains 963 plugins.
      Simple pytest plugin to look for regex in Exceptions
 
   :pypi:`pytest-raisin`
-     *last release*: Jun 25, 2020,
+     *last release*: Feb 06, 2022,
      *status*: N/A,
      *requires*: pytest
 
@@ -5747,7 +9608,7 @@ This list contains 963 plugins.
      py.test plugin to randomize tests
 
   :pypi:`pytest-randomly`
-     *last release*: Nov 30, 2021,
+     *last release*: Oct 25, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
@@ -5768,30 +9629,44 @@ This list contains 963 plugins.
      Randomise the order in which pytest tests are run with some control over the randomness
 
   :pypi:`pytest-random-order`
-     *last release*: Nov 30, 2018,
+     *last release*: Jan 20, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.0.0)
+     *requires*: pytest >=3.0.0
 
      Randomise the order in which pytest tests are run with some control over the randomness
 
+  :pypi:`pytest-ranking`
+     *last release*: Jan 14, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.4.3
+
+     A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection
+
   :pypi:`pytest-readme`
-     *last release*: Dec 28, 2014,
+     *last release*: Sep 02, 2022,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
      Test your README.md file
 
   :pypi:`pytest-reana`
-     *last release*: Nov 22, 2021,
+     *last release*: Sep 04, 2024,
      *status*: 3 - Alpha,
      *requires*: N/A
 
      Pytest fixtures for REANA.
 
+  :pypi:`pytest-recorder`
+     *last release*: Mar 31, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs.
+
   :pypi:`pytest-recording`
-     *last release*: Jul 08, 2021,
+     *last release*: Jul 09, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest>=3.5.0
 
      A pytest plugin that allows you recording of network interactions via VCR.py
 
@@ -5802,15 +9677,22 @@ This list contains 963 plugins.
 
      Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal
 
+  :pypi:`pytest-record-video`
+     *last release*: Oct 31, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     用例执行过程中录制视频
+
   :pypi:`pytest-redis`
-     *last release*: Nov 03, 2021,
+     *last release*: Nov 27, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest
+     *requires*: pytest>=6.2
 
      Redis fixtures and fixture factories for Pytest.
 
   :pypi:`pytest-redislite`
-     *last release*: Sep 19, 2021,
+     *last release*: Apr 05, 2022,
      *status*: 4 - Beta,
      *requires*: pytest
 
@@ -5837,19 +9719,33 @@ This list contains 963 plugins.
 
      Conveniently run pytest with a dot-formatted test reference.
 
+  :pypi:`pytest-regex`
+     *last release*: May 29, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     Select pytest tests with regular expressions
+
+  :pypi:`pytest-regex-dependency`
+     *last release*: Jun 12, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     Management of Pytest dependencies via regex patterns
+
   :pypi:`pytest-regressions`
-     *last release*: Jan 27, 2021,
+     *last release*: Jan 10, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest>=6.2.0
 
      Easy to use fixtures to write regression tests.
 
   :pypi:`pytest-regtest`
-     *last release*: Jun 03, 2021,
+     *last release*: Nov 12, 2024,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest>7.2
 
-     pytest plugin for regression tests
+     pytest plugin for snapshot regression testing
 
   :pypi:`pytest-relative-order`
      *last release*: May 17, 2021,
@@ -5858,10 +9754,17 @@ This list contains 963 plugins.
 
      a pytest plugin that sorts tests using "before" and "after" markers
 
+  :pypi:`pytest-relative-path`
+     *last release*: Aug 30, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Handle relative path in pytest options or ini configs
+
   :pypi:`pytest-relaxed`
-     *last release*: Jun 14, 2019,
+     *last release*: Mar 29, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (<5,>=3)
+     *requires*: pytest>=7
 
      Relaxed test discovery/organization for pytest
 
@@ -5873,21 +9776,21 @@ This list contains 963 plugins.
      Pytest plugin to create a temporary directory with remote files
 
   :pypi:`pytest-remotedata`
-     *last release*: Jul 20, 2019,
-     *status*: 3 - Alpha,
-     *requires*: pytest (>=3.1)
+     *last release*: Sep 26, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest >=4.6
 
      Pytest plugin for controlling remote data access.
 
   :pypi:`pytest-remote-response`
-     *last release*: Jun 30, 2021,
-     *status*: 4 - Beta,
+     *last release*: Apr 26, 2023,
+     *status*: 5 - Production/Stable,
      *requires*: pytest (>=4.6)
 
      Pytest plugin for capturing and mocking connection requests.
 
   :pypi:`pytest-remove-stale-bytecode`
-     *last release*: Mar 04, 2020,
+     *last release*: Jul 07, 2023,
      *status*: 4 - Beta,
      *requires*: pytest
 
@@ -5901,21 +9804,28 @@ This list contains 963 plugins.
      Reorder tests depending on their paths and names.
 
   :pypi:`pytest-repeat`
-     *last release*: Oct 31, 2020,
+     *last release*: Oct 09, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.6)
+     *requires*: pytest
 
      pytest plugin for repeating tests
 
+  :pypi:`pytest_repeater`
+     *last release*: Feb 09, 2018,
+     *status*: 1 - Planning,
+     *requires*: N/A
+
+     py.test plugin for repeating single test multiple times.
+
   :pypi:`pytest-replay`
-     *last release*: Jun 09, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=3.0.0)
+     *last release*: Feb 05, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
 
      Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests
 
   :pypi:`pytest-repo-health`
-     *last release*: Nov 23, 2021,
+     *last release*: Mar 12, 2025,
      *status*: 3 - Alpha,
      *requires*: pytest
 
@@ -5929,19 +9839,33 @@ This list contains 963 plugins.
      Creates json report that is compatible with atom.io's linter message format
 
   :pypi:`pytest-reporter`
-     *last release*: Jul 22, 2021,
+     *last release*: Feb 28, 2024,
      *status*: 4 - Beta,
      *requires*: pytest
 
      Generate Pytest reports with templates
 
   :pypi:`pytest-reporter-html1`
-     *last release*: Jun 08, 2021,
+     *last release*: Oct 11, 2024,
      *status*: 4 - Beta,
      *requires*: N/A
 
      A basic HTML report template for Pytest
 
+  :pypi:`pytest-reporter-html-dots`
+     *last release*: Jan 22, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     A basic HTML report for pytest using Jinja2 template engine.
+
+  :pypi:`pytest-report-extras`
+     *last release*: Apr 04, 2025,
+     *status*: N/A,
+     *requires*: pytest>=8.0.0
+
+     Pytest plugin to enhance pytest-html and allure reports by adding comments, screenshots, webpage sources and attachments.
+
   :pypi:`pytest-reportinfra`
      *last release*: Aug 11, 2019,
      *status*: 3 - Alpha,
@@ -5957,9 +9881,9 @@ This list contains 963 plugins.
      A plugin to report summarized results in a table format
 
   :pypi:`pytest-reportlog`
-     *last release*: Dec 11, 2020,
+     *last release*: May 22, 2023,
      *status*: 3 - Alpha,
-     *requires*: pytest (>=5.2)
+     *requires*: pytest
 
      Replacement for the --resultlog option, focused in simplicity and extensibility
 
@@ -5978,12 +9902,33 @@ This list contains 963 plugins.
      pytest plugin for adding tests' parameters to junit report
 
   :pypi:`pytest-reportportal`
-     *last release*: Jun 18, 2021,
+     *last release*: Feb 28, 2025,
      *status*: N/A,
-     *requires*: pytest (>=3.8.0)
+     *requires*: pytest>=4.6.10
 
      Agent for Reporting results of tests to the Report Portal
 
+  :pypi:`pytest-report-stream`
+     *last release*: Oct 22, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     A pytest plugin which allows to stream test reports at runtime
+
+  :pypi:`pytest-repo-structure`
+     *last release*: Mar 18, 2024,
+     *status*: 1 - Planning,
+     *requires*: N/A
+
+     Pytest Repo Structure
+
+  :pypi:`pytest-req`
+     *last release*: Aug 31, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest<9.0.0,>=8.3.2
+
+     pytest requests plugin
+
   :pypi:`pytest-reqs`
      *last release*: May 12, 2019,
      *status*: N/A,
@@ -5998,8 +9943,36 @@ This list contains 963 plugins.
 
      A simple plugin to use with pytest
 
+  :pypi:`pytest-requestselapsed`
+     *last release*: Aug 14, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     collect and show http requests elapsed time
+
+  :pypi:`pytest-requests-futures`
+     *last release*: Jul 06, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Pytest Plugin to Mock Requests Futures
+
+  :pypi:`pytest-requirements`
+     *last release*: Feb 28, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     pytest plugin for using custom markers to relate tests to requirements and usecases
+
+  :pypi:`pytest-requires`
+     *last release*: Dec 21, 2021,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     A pytest plugin to elegantly skip tests with optional requirements
+
   :pypi:`pytest-reraise`
-     *last release*: Jun 17, 2021,
+     *last release*: Sep 20, 2022,
      *status*: 5 - Production/Stable,
      *requires*: pytest (>=4.6)
 
@@ -6012,69 +9985,216 @@ This list contains 963 plugins.
 
      Re-run only changed files in specified branch
 
+  :pypi:`pytest-rerun-all`
+     *last release*: Nov 16, 2023,
+     *status*: 3 - Alpha,
+     *requires*: pytest (>=7.0.0)
+
+     Rerun testsuite for a certain time or iterations
+
+  :pypi:`pytest-rerunclassfailures`
+     *last release*: Apr 24, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.2
+
+     pytest rerun class failures plugin
+
   :pypi:`pytest-rerunfailures`
-     *last release*: Sep 17, 2021,
+     *last release*: Nov 20, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest!=8.2.2,>=7.4
+
+     pytest plugin to re-run tests to eliminate flaky failures
+
+  :pypi:`pytest-rerunfailures-all-logs`
+     *last release*: Mar 07, 2022,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.3)
+     *requires*: N/A
 
      pytest plugin to re-run tests to eliminate flaky failures
 
-  :pypi:`pytest-resilient-circuits`
-     *last release*: Nov 15, 2021,
+  :pypi:`pytest-reserial`
+     *last release*: Dec 22, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Pytest fixture for recording and replaying serial port traffic.
+
+  :pypi:`pytest-resilient-circuits`
+     *last release*: Feb 28, 2025,
+     *status*: N/A,
+     *requires*: pytest~=7.0
+
+     Resilient Circuits fixtures for PyTest
+
+  :pypi:`pytest-resource`
+     *last release*: Nov 14, 2018,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Load resource fixture plugin to use with pytest
+
+  :pypi:`pytest-resource-path`
+     *last release*: May 01, 2021,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=3.5.0)
+
+     Provides path for uniform access to test resources in isolated directory
+
+  :pypi:`pytest-resource-usage`
+     *last release*: Nov 06, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.0.0
+
+     Pytest plugin for reporting running time and peak memory usage
+
+  :pypi:`pytest-responsemock`
+     *last release*: Mar 10, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Simplified requests calls mocking for pytest
+
+  :pypi:`pytest-responses`
+     *last release*: Oct 11, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=2.5)
+
+     py.test integration for responses
+
+  :pypi:`pytest-rest-api`
+     *last release*: Aug 08, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.1.2,<8.0.0)
+
+
+
+  :pypi:`pytest-restrict`
+     *last release*: Oct 24, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Pytest plugin to restrict the test types allowed
+
+  :pypi:`pytest-result-log`
+     *last release*: Jan 10, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.2.0
+
+     A pytest plugin that records the start, end, and result information of each use case in a log file
+
+  :pypi:`pytest-results`
+     *last release*: Mar 14, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     Easily spot regressions in your tests.
+
+  :pypi:`pytest-result-sender`
+     *last release*: Apr 20, 2023,
+     *status*: N/A,
+     *requires*: pytest>=7.3.1
+
+
+
+  :pypi:`pytest-result-sender-lj`
+     *last release*: Dec 17, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8.3.4
+
+     Default template for PDM package
+
+  :pypi:`pytest-result-sender-lyt`
+     *last release*: Mar 14, 2025,
+     *status*: N/A,
+     *requires*: pytest>=8.3.5
+
+     Default template for PDM package
+
+  :pypi:`pytest-result-sender-misszhang`
+     *last release*: Mar 21, 2025,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest>=8.3.5
 
-     Resilient Circuits fixtures for PyTest.
+     Default template for PDM package
 
-  :pypi:`pytest-resource`
-     *last release*: Nov 14, 2018,
+  :pypi:`pytest-resume`
+     *last release*: Apr 22, 2023,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest (>=7.0)
 
-     Load resource fixture plugin to use with pytest
+     A Pytest plugin to resuming from the last run test
 
-  :pypi:`pytest-resource-path`
-     *last release*: May 01, 2021,
-     *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.5.0)
+  :pypi:`pytest-rethinkdb`
+     *last release*: Jul 24, 2016,
+     *status*: 4 - Beta,
+     *requires*: N/A
 
-     Provides path for uniform access to test resources in isolated directory
+     A RethinkDB plugin for pytest.
 
-  :pypi:`pytest-responsemock`
-     *last release*: Oct 10, 2020,
-     *status*: 5 - Production/Stable,
-     *requires*: N/A
+  :pypi:`pytest-retry`
+     *last release*: Jan 19, 2025,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
 
-     Simplified requests calls mocking for pytest
+     Adds the ability to retry flaky tests in CI environments
 
-  :pypi:`pytest-responses`
-     *last release*: Apr 26, 2021,
+  :pypi:`pytest-retry-class`
+     *last release*: Nov 24, 2024,
      *status*: N/A,
-     *requires*: pytest (>=2.5)
+     *requires*: pytest>=5.3
 
-     py.test integration for responses
+     A pytest plugin to rerun entire class on failure
 
-  :pypi:`pytest-restrict`
-     *last release*: Aug 12, 2021,
-     *status*: 5 - Production/Stable,
-     *requires*: pytest
+  :pypi:`pytest-reusable-testcases`
+     *last release*: Apr 28, 2023,
+     *status*: N/A,
+     *requires*: N/A
 
-     Pytest plugin to restrict the test types allowed
 
-  :pypi:`pytest-rethinkdb`
-     *last release*: Jul 24, 2016,
+
+  :pypi:`pytest-revealtype-injector`
+     *last release*: Mar 18, 2025,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest<9,>=7.0
 
-     A RethinkDB plugin for pytest.
+     Pytest plugin for replacing reveal_type() calls inside test functions with static and runtime type checking result comparison, for confirming type annotation validity.
 
   :pypi:`pytest-reverse`
-     *last release*: Aug 12, 2021,
+     *last release*: Oct 25, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      Pytest plugin to reverse test order.
 
+  :pypi:`pytest-rich`
+     *last release*: Dec 12, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.0
+
+     Leverage rich for richer test session output
+
+  :pypi:`pytest-richer`
+     *last release*: Oct 27, 2023,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     Pytest plugin providing a Rich based reporter.
+
+  :pypi:`pytest-rich-reporter`
+     *last release*: Feb 17, 2022,
+     *status*: 1 - Planning,
+     *requires*: pytest (>=5.0.0)
+
+     A pytest plugin using Rich for beautiful test result formatting.
+
+  :pypi:`pytest-richtrace`
+     *last release*: Jun 20, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     A pytest plugin that displays the names and information of the pytest hook functions as they are executed.
+
   :pypi:`pytest-ringo`
      *last release*: Sep 27, 2017,
      *status*: 3 - Alpha,
@@ -6082,6 +10202,13 @@ This list contains 963 plugins.
 
      pytest plugin to test webapplications using the Ringo webframework
 
+  :pypi:`pytest-rmsis`
+     *last release*: Aug 10, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=5.3.5)
+
+     Sycronise pytest results to Jira RMsis
+
   :pypi:`pytest-rng`
      *last release*: Aug 08, 2019,
      *status*: 5 - Production/Stable,
@@ -6090,12 +10217,19 @@ This list contains 963 plugins.
      Fixtures for seeding tests and making randomness reproducible
 
   :pypi:`pytest-roast`
-     *last release*: Jul 29, 2021,
+     *last release*: Nov 09, 2022,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      pytest plugin for ROAST configuration override and fixtures
 
+  :pypi:`pytest_robotframework`
+     *last release*: Mar 28, 2025,
+     *status*: N/A,
+     *requires*: pytest<9,>=7
+
+     a pytest plugin that can run both python and robotframework tests while generating robot reports for them
+
   :pypi:`pytest-rocketchat`
      *last release*: Apr 18, 2021,
      *status*: 5 - Production/Stable,
@@ -6118,14 +10252,14 @@ This list contains 963 plugins.
      Extend py.test for RPC OpenStack testing.
 
   :pypi:`pytest-rst`
-     *last release*: Sep 21, 2021,
+     *last release*: Jan 26, 2023,
      *status*: N/A,
-     *requires*: pytest
+     *requires*: N/A
 
      Test code from RST documents with pytest
 
   :pypi:`pytest-rt`
-     *last release*: Sep 04, 2021,
+     *last release*: May 05, 2022,
      *status*: N/A,
      *requires*: N/A
 
@@ -6138,6 +10272,13 @@ This list contains 963 plugins.
 
      Coverage-based regression test selection (RTS) plugin for pytest
 
+  :pypi:`pytest-ruff`
+     *last release*: Jul 21, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=5
+
+     pytest plugin to check ruff requirements.
+
   :pypi:`pytest-run-changed`
      *last release*: Apr 02, 2021,
      *status*: 3 - Alpha,
@@ -6152,20 +10293,48 @@ This list contains 963 plugins.
 
      implement a --failed option for pytest
 
-  :pypi:`pytest-runner`
-     *last release*: May 19, 2021,
+  :pypi:`pytest-run-parallel`
+     *last release*: Feb 05, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A simple pytest plugin to run tests concurrently
+
+  :pypi:`pytest-run-subprocess`
+     *last release*: Nov 12, 2022,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=4.6) ; extra == 'testing'
+     *requires*: pytest
+
+     Pytest Plugin for running and testing subprocesses.
+
+  :pypi:`pytest-runtime-types`
+     *last release*: Feb 09, 2023,
+     *status*: N/A,
+     *requires*: pytest
 
-     Invoke py.test as distutils command with dependency resolution
+     Checks type annotations on runtime while running tests.
 
   :pypi:`pytest-runtime-xfail`
      *last release*: Aug 26, 2021,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest>=5.0.0
 
      Call runtime_xfail() to mark running test as xfail.
 
+  :pypi:`pytest-runtime-yoyo`
+     *last release*: Jun 12, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.2.0)
+
+     run case mark timeout
+
+  :pypi:`pytest-saccharin`
+     *last release*: Oct 31, 2022,
+     *status*: 3 - Alpha,
+     *requires*: N/A
+
+     pytest-saccharin is a updated fork of pytest-sugar, a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly).
+
   :pypi:`pytest-salt`
      *last release*: Jan 27, 2020,
      *status*: 4 - Beta,
@@ -6181,9 +10350,9 @@ This list contains 963 plugins.
      A Pytest plugin that builds and creates docker containers
 
   :pypi:`pytest-salt-factories`
-     *last release*: Sep 16, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=6.0.0)
+     *last release*: Oct 22, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.4.0
 
      Pytest Salt Plugin
 
@@ -6201,6 +10370,13 @@ This list contains 963 plugins.
 
      Simple PyTest Plugin For Salt's Test Suite Specifically
 
+  :pypi:`pytest-sample-argvalues`
+     *last release*: May 07, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     A utility function to help choose a random sample from your argvalues in pytest.
+
   :pypi:`pytest-sanic`
      *last release*: Oct 25, 2021,
      *status*: N/A,
@@ -6208,6 +10384,13 @@ This list contains 963 plugins.
 
      a pytest plugin for Sanic
 
+  :pypi:`pytest-sanitizer`
+     *last release*: Mar 16, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest>=6.0.0
+
+     A pytest plugin to sanitize output for LLMs (personal tool, no warranty or liability)
+
   :pypi:`pytest-sanity`
      *last release*: Dec 07, 2020,
      *status*: N/A,
@@ -6222,8 +10405,15 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest_sauce`
+     *last release*: Jul 14, 2014,
+     *status*: 3 - Alpha,
+     *requires*: N/A
+
+     pytest_sauce provides sane and helpful methods worked    out in clearcode to run py.test tests with selenium/saucelabs
+
   :pypi:`pytest-sbase`
-     *last release*: Dec 03, 2021,
+     *last release*: Apr 04, 2025,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
@@ -6236,13 +10426,48 @@ This list contains 963 plugins.
 
      pytest plugin for test scenarios
 
+  :pypi:`pytest-scenario-files`
+     *last release*: Nov 21, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.0
+
+     A pytest plugin that generates unit test scenarios from data files.
+
+  :pypi:`pytest-schedule`
+     *last release*: Oct 31, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Automate and customize test scheduling effortlessly on local machines.
+
   :pypi:`pytest-schema`
-     *last release*: Aug 31, 2020,
+     *last release*: Feb 16, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest >=3.5.0
 
      👍 Validate return values against a schema-like object in testing
 
+  :pypi:`pytest-scim2-server`
+     *last release*: Mar 28, 2025,
+     *status*: N/A,
+     *requires*: pytest>=8.3.4
+
+     SCIM2 server fixture for Pytest
+
+  :pypi:`pytest-screenshot-on-failure`
+     *last release*: Jul 21, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Saves a screenshot when a test case from a pytest execution fails
+
+  :pypi:`pytest-scrutinize`
+     *last release*: Aug 19, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6
+
+     Scrutinize your pytest test suites for slow fixtures, tests and more.
+
   :pypi:`pytest-securestore`
      *last release*: Nov 08, 2021,
      *status*: 4 - Beta,
@@ -6258,21 +10483,28 @@ This list contains 963 plugins.
      A pytest plugin which allows to (de-)select tests from a file.
 
   :pypi:`pytest-selenium`
-     *last release*: Sep 19, 2020,
+     *last release*: Feb 01, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.0.0)
+     *requires*: pytest>=6.0.0
 
      pytest plugin for Selenium
 
+  :pypi:`pytest-selenium-auto`
+     *last release*: Nov 07, 2023,
+     *status*: N/A,
+     *requires*: pytest >= 7.0.0
+
+     pytest plugin to automatically capture screenshots upon selenium webdriver events
+
   :pypi:`pytest-seleniumbase`
-     *last release*: Dec 03, 2021,
+     *last release*: Apr 04, 2025,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
      A complete web automation framework for end-to-end testing.
 
   :pypi:`pytest-selenium-enhancer`
-     *last release*: Nov 26, 2020,
+     *last release*: Apr 29, 2022,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
@@ -6285,34 +10517,69 @@ This list contains 963 plugins.
 
      A pytest package implementing perceptualdiff for Selenium tests.
 
+  :pypi:`pytest-selfie`
+     *last release*: Dec 16, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8.0.0
+
+     A pytest plugin for selfie snapshot testing.
+
   :pypi:`pytest-send-email`
-     *last release*: Dec 04, 2019,
+     *last release*: Sep 02, 2024,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest
 
      Send pytest execution result email
 
   :pypi:`pytest-sentry`
-     *last release*: Apr 21, 2021,
+     *last release*: Feb 24, 2025,
      *status*: N/A,
      *requires*: pytest
 
      A pytest plugin to send testrun information to Sentry.io
 
+  :pypi:`pytest-sequence-markers`
+     *last release*: May 23, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Pytest plugin for sequencing markers for execution of tests
+
+  :pypi:`pytest-server`
+     *last release*: Sep 09, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     test server exec cmd
+
   :pypi:`pytest-server-fixtures`
-     *last release*: May 28, 2019,
+     *last release*: Nov 29, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
-     Extensible server fixures for py.test
+     Extensible server fixtures for py.test
 
   :pypi:`pytest-serverless`
-     *last release*: Nov 27, 2021,
+     *last release*: May 09, 2022,
      *status*: 4 - Beta,
      *requires*: N/A
 
      Automatically mocks resources from serverless.yml in pytest using moto.
 
+  :pypi:`pytest-servers`
+     *last release*: Mar 12, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest>=6.2
+
+     pytest servers
+
+  :pypi:`pytest-service`
+     *last release*: Aug 06, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=6.0.0
+
+
+
   :pypi:`pytest-services`
      *last release*: Oct 30, 2020,
      *status*: 6 - Mature,
@@ -6341,6 +10608,13 @@ This list contains 963 plugins.
 
      pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test.
 
+  :pypi:`pytest-setupinfo`
+     *last release*: Jan 23, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+     Displaying setup info during pytest command run
+
   :pypi:`pytest-sftpserver`
      *last release*: Sep 16, 2019,
      *status*: 4 - Beta,
@@ -6355,13 +10629,41 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-shared-session-scope`
+     *last release*: Sep 22, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
+
+     Pytest session-scoped fixture that works with xdist
+
+  :pypi:`pytest-share-hdf`
+     *last release*: Sep 21, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     Plugin to save test data in HDF files and retrieve them for comparison
+
+  :pypi:`pytest-sharkreport`
+     *last release*: Jul 11, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=3.5)
+
+     this is pytest report plugin.
+
   :pypi:`pytest-shell`
-     *last release*: Nov 07, 2021,
+     *last release*: Mar 27, 2022,
      *status*: N/A,
      *requires*: N/A
 
      A pytest plugin to help with testing shell scripts / black box commands
 
+  :pypi:`pytest-shell-utilities`
+     *last release*: Oct 22, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.4.0
+
+     Pytest plugin to simplify running shell commands against the system
+
   :pypi:`pytest-sheraf`
      *last release*: Feb 11, 2020,
      *status*: N/A,
@@ -6370,9 +10672,9 @@ This list contains 963 plugins.
      Versatile ZODB abstraction layer - pytest fixtures
 
   :pypi:`pytest-sherlock`
-     *last release*: Nov 18, 2021,
+     *last release*: Aug 14, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.5.1)
+     *requires*: pytest >=3.5.1
 
      pytest plugin help to find coupled tests
 
@@ -6384,12 +10686,19 @@ This list contains 963 plugins.
      Expand command-line shortcuts listed in pytest configuration
 
   :pypi:`pytest-shutil`
-     *last release*: May 28, 2019,
+     *last release*: Nov 29, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      A goodie-bag of unix shell and environment tools for py.test
 
+  :pypi:`pytest-simbind`
+     *last release*: Mar 28, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
+
+     Pytest plugin to operate with objects generated by Simbind tool.
+
   :pypi:`pytest-simplehttpserver`
      *last release*: Jun 24, 2021,
      *status*: 4 - Beta,
@@ -6418,10 +10727,17 @@ This list contains 963 plugins.
 
      Allow for multiple processes to log to a single file
 
+  :pypi:`pytest-skip`
+     *last release*: Apr 04, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+     A pytest plugin which allows to (de-)select or skip tests from a file.
+
   :pypi:`pytest-skip-markers`
-     *last release*: Oct 04, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=6.0.0)
+     *last release*: Aug 09, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=7.1.0
 
      Pytest Salt Plugin
 
@@ -6440,12 +10756,19 @@ This list contains 963 plugins.
      Automatically skip tests that don't need to run!
 
   :pypi:`pytest-skip-slow`
-     *last release*: Sep 28, 2021,
+     *last release*: Feb 09, 2023,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest>=6.2.0
 
      A pytest plugin to skip \`@pytest.mark.slow\` tests by default.
 
+  :pypi:`pytest-skipuntil`
+     *last release*: Nov 25, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest >=3.8.0
+
+     A simple pytest plugin to skip flapping test with deadline
+
   :pypi:`pytest-slack`
      *last release*: Dec 15, 2020,
      *status*: 5 - Production/Stable,
@@ -6460,6 +10783,27 @@ This list contains 963 plugins.
 
      A pytest plugin to skip \`@pytest.mark.slow\` tests by default.
 
+  :pypi:`pytest-slowest-first`
+     *last release*: Dec 11, 2022,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Sort tests by their last duration, slowest first
+
+  :pypi:`pytest-slow-first`
+     *last release*: Jan 30, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest >=3.5.0
+
+     Prioritize running the slowest tests first.
+
+  :pypi:`pytest-slow-last`
+     *last release*: Mar 16, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=3.5.0
+
+     Run tests in order of execution time (faster tests first)
+
   :pypi:`pytest-smartcollect`
      *last release*: Oct 04, 2018,
      *status*: N/A,
@@ -6474,6 +10818,20 @@ This list contains 963 plugins.
 
      Smart coverage plugin for pytest.
 
+  :pypi:`pytest-smell`
+     *last release*: Jun 26, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Automated bad smell detection tool for Pytest
+
+  :pypi:`pytest-smoke`
+     *last release*: Mar 25, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest<9,>=7.0.0
+
+     Pytest plugin for smoke testing
+
   :pypi:`pytest-smtp`
      *last release*: Feb 20, 2021,
      *status*: N/A,
@@ -6481,6 +10839,27 @@ This list contains 963 plugins.
 
      Send email with pytest execution result
 
+  :pypi:`pytest-smtp4dev`
+     *last release*: Jun 27, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Plugin for smtp4dev API
+
+  :pypi:`pytest-smtpd`
+     *last release*: May 15, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     An SMTP server for testing built on aiosmtpd
+
+  :pypi:`pytest-smtp-test-server`
+     *last release*: Dec 03, 2023,
+     *status*: 2 - Pre-Alpha,
+     *requires*: pytest (>=7.4.3,<8.0.0)
+
+     pytest plugin for using \`smtp-test-server\` as a fixture
+
   :pypi:`pytest-snail`
      *last release*: Nov 04, 2019,
      *status*: 3 - Alpha,
@@ -6495,8 +10874,22 @@ This list contains 963 plugins.
 
      py.test plugin for Snap-CI
 
+  :pypi:`pytest-snapmock`
+     *last release*: Nov 15, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Snapshots for your mocks.
+
   :pypi:`pytest-snapshot`
-     *last release*: Dec 02, 2021,
+     *last release*: Apr 23, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.0.0)
+
+     A plugin for snapshot testing with pytest.
+
+  :pypi:`pytest-snapshot-with-message-generator`
+     *last release*: Jul 25, 2023,
      *status*: 4 - Beta,
      *requires*: pytest (>=3.0.0)
 
@@ -6509,13 +10902,34 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-snob`
+     *last release*: Jan 12, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     A pytest plugin that only selects meaningful python tests to run.
+
+  :pypi:`pytest-snowflake-bdd`
+     *last release*: Jan 05, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=6.2.0)
+
+     Setup test data and run tests on snowflake in BDD style!
+
   :pypi:`pytest-socket`
-     *last release*: Aug 28, 2021,
+     *last release*: Jan 28, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=3.6.3)
+     *requires*: pytest (>=6.2.5)
 
      Pytest Plugin to disable socket calls during tests
 
+  :pypi:`pytest-sofaepione`
+     *last release*: Aug 17, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Test the installation of SOFA and the SofaEpione plugin.
+
   :pypi:`pytest-soft-assertions`
      *last release*: May 05, 2020,
      *status*: 3 - Alpha,
@@ -6523,6 +10937,13 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-solidity`
+     *last release*: Jan 15, 2022,
+     *status*: 1 - Planning,
+     *requires*: pytest (<7,>=6.0.1) ; extra == 'tests'
+
+     A PyTest library plugin for Solidity language.
+
   :pypi:`pytest-solr`
      *last release*: May 11, 2020,
      *status*: 3 - Alpha,
@@ -6530,6 +10951,13 @@ This list contains 963 plugins.
 
      Solr process and client fixtures for py.test.
 
+  :pypi:`pytest-sort`
+     *last release*: Mar 22, 2025,
+     *status*: N/A,
+     *requires*: pytest>=7.4.0
+
+     Tools for sorting test cases
+
   :pypi:`pytest-sorter`
      *last release*: Apr 20, 2021,
      *status*: 4 - Beta,
@@ -6537,6 +10965,13 @@ This list contains 963 plugins.
 
      A simple plugin to first execute tests that historically failed more
 
+  :pypi:`pytest-sosu`
+     *last release*: Aug 04, 2023,
+     *status*: 2 - Pre-Alpha,
+     *requires*: pytest
+
+     Unofficial PyTest plugin for Sauce Labs
+
   :pypi:`pytest-sourceorder`
      *last release*: Sep 01, 2021,
      *status*: 4 - Beta,
@@ -6545,7 +10980,7 @@ This list contains 963 plugins.
      Test-ordering plugin for pytest
 
   :pypi:`pytest-spark`
-     *last release*: Feb 23, 2020,
+     *last release*: Mar 21, 2025,
      *status*: 4 - Beta,
      *requires*: pytest
 
@@ -6559,37 +10994,65 @@ This list contains 963 plugins.
      py.test plugin to spawn process and communicate with them.
 
   :pypi:`pytest-spec`
-     *last release*: May 04, 2021,
+     *last release*: Aug 04, 2024,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest; extra == "test"
 
      Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION.
 
+  :pypi:`pytest-spec2md`
+     *last release*: Apr 10, 2024,
+     *status*: N/A,
+     *requires*: pytest>7.0
+
+     Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest.
+
+  :pypi:`pytest-speed`
+     *last release*: Jan 22, 2023,
+     *status*: 3 - Alpha,
+     *requires*: pytest>=7
+
+     Modern benchmarking library for python with pytest integration.
+
   :pypi:`pytest-sphinx`
-     *last release*: Aug 05, 2020,
+     *last release*: Apr 13, 2024,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest>=8.1.1
 
      Doctest plugin for pytest with support for Sphinx-specific doctest-directives
 
   :pypi:`pytest-spiratest`
-     *last release*: Oct 13, 2021,
+     *last release*: Jan 01, 2024,
      *status*: N/A,
      *requires*: N/A
 
-     Exports unit tests as test runs in SpiraTest/Team/Plan
+     Exports unit tests as test runs in Spira (SpiraTest/Team/Plan)
 
   :pypi:`pytest-splinter`
-     *last release*: Dec 25, 2020,
+     *last release*: Sep 09, 2022,
      *status*: 6 - Mature,
-     *requires*: N/A
+     *requires*: pytest (>=3.0.0)
 
      Splinter plugin for pytest testing framework
 
+  :pypi:`pytest-splinter4`
+     *last release*: Feb 01, 2024,
+     *status*: 6 - Mature,
+     *requires*: pytest >=8.0.0
+
+     Pytest plugin for the splinter automation library
+
   :pypi:`pytest-split`
-     *last release*: Nov 09, 2021,
+     *last release*: Oct 16, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest<9,>=5
+
+     Pytest plugin which splits the test suite to equally sized sub suites based on test execution time.
+
+  :pypi:`pytest-split-ext`
+     *last release*: Sep 23, 2023,
      *status*: 4 - Beta,
-     *requires*: pytest (>=5,<7)
+     *requires*: pytest (>=5,<8)
 
      Pytest plugin which splits the test suite to equally sized sub suites based on test execution time.
 
@@ -6615,14 +11078,14 @@ This list contains 963 plugins.
 
 
   :pypi:`pytest-splunk-addon`
-     *last release*: Nov 29, 2021,
+     *last release*: Mar 07, 2025,
      *status*: N/A,
-     *requires*: pytest (>5.4.0,<6.3)
+     *requires*: pytest<8,>5.4.0
 
      A Dynamic test tool for Splunk Apps and Add-ons
 
   :pypi:`pytest-splunk-addon-ui-smartx`
-     *last release*: Oct 07, 2021,
+     *last release*: Mar 19, 2025,
      *status*: N/A,
      *requires*: N/A
 
@@ -6649,6 +11112,20 @@ This list contains 963 plugins.
 
      pytest plugin with sqlalchemy related fixtures
 
+  :pypi:`pytest-sqlalchemy-mock`
+     *last release*: Aug 10, 2024,
+     *status*: 3 - Alpha,
+     *requires*: pytest>=7.0.0
+
+     pytest sqlalchemy plugin for mock
+
+  :pypi:`pytest-sqlalchemy-session`
+     *last release*: May 19, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=7.0)
+
+     A pytest plugin for preserving test isolation that use SQLAlchemy.
+
   :pypi:`pytest-sql-bigquery`
      *last release*: Dec 19, 2019,
      *status*: N/A,
@@ -6656,10 +11133,31 @@ This list contains 963 plugins.
 
      Yet another SQL-testing framework for BigQuery provided by pytest plugin
 
+  :pypi:`pytest-sqlfluff`
+     *last release*: Dec 21, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=3.5.0)
+
+     A pytest plugin to use sqlfluff to enable format checking of sql files.
+
+  :pypi:`pytest-sqlguard`
+     *last release*: Mar 11, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7
+
+     Pytest fixture to record and check SQL Queries made by SQLAlchemy
+
+  :pypi:`pytest-squadcast`
+     *last release*: Feb 22, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Pytest report plugin for Squadcast
+
   :pypi:`pytest-srcpaths`
      *last release*: Oct 15, 2021,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest>=6.2.0
 
      Add paths to sys.path
 
@@ -6677,6 +11175,27 @@ This list contains 963 plugins.
 
      Start pytest run from a given point
 
+  :pypi:`pytest-star-track-issue`
+     *last release*: Feb 20, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     A package to prevent Dependency Confusion attacks against Yandex.
+
+  :pypi:`pytest-static`
+     *last release*: Oct 20, 2024,
+     *status*: 1 - Planning,
+     *requires*: pytest<8.0.0,>=7.4.3
+
+     pytest-static
+
+  :pypi:`pytest-stats`
+     *last release*: Jul 18, 2024,
+     *status*: N/A,
+     *requires*: pytest>=8.0.0
+
+     Collects tests metadata for future analysis, easy to extend for any data store
+
   :pypi:`pytest-statsd`
      *last release*: Nov 30, 2018,
      *status*: 5 - Production/Stable,
@@ -6684,6 +11203,13 @@ This list contains 963 plugins.
 
      pytest plugin for reporting to graphite
 
+  :pypi:`pytest-status`
+     *last release*: Aug 22, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Add status mark for tests
+
   :pypi:`pytest-stepfunctions`
      *last release*: May 08, 2021,
      *status*: 4 - Beta,
@@ -6705,6 +11231,20 @@ This list contains 963 plugins.
 
      Run a test suite one failing test at a time.
 
+  :pypi:`pytest-stf`
+     *last release*: Sep 24, 2024,
+     *status*: N/A,
+     *requires*: pytest>=5.0
+
+     pytest plugin for openSTF
+
+  :pypi:`pytest-stochastics`
+     *last release*: Dec 01, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.0.0
+
+     pytest plugin that allows selectively running tests several times and accepting \*some\* failures.
+
   :pypi:`pytest-stoq`
      *last release*: Feb 09, 2021,
      *status*: 4 - Beta,
@@ -6712,6 +11252,13 @@ This list contains 963 plugins.
 
      A plugin to pytest stoq
 
+  :pypi:`pytest-store`
+     *last release*: Sep 04, 2024,
+     *status*: 3 - Alpha,
+     *requires*: pytest>=7.0.0
+
+     Pytest plugin to store values from test runs
+
   :pypi:`pytest-stress`
      *last release*: Dec 07, 2019,
      *status*: 4 - Beta,
@@ -6720,7 +11267,7 @@ This list contains 963 plugins.
      A Pytest plugin that allows you to loop tests for a user defined amount of time.
 
   :pypi:`pytest-structlog`
-     *last release*: Sep 21, 2021,
+     *last release*: Jul 25, 2024,
      *status*: N/A,
      *requires*: pytest
 
@@ -6754,57 +11301,71 @@ This list contains 963 plugins.
 
      A pytest plugin to organize long run tests (named studies) without interfering the regular tests
 
+  :pypi:`pytest-subinterpreter`
+     *last release*: Nov 25, 2023,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
+
+     Run pytest in a subinterpreter
+
   :pypi:`pytest-subprocess`
-     *last release*: Nov 07, 2021,
+     *last release*: Jan 04, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=4.0.0)
+     *requires*: pytest>=4.0.0
 
      A plugin to fake subprocess for pytest
 
   :pypi:`pytest-subtesthack`
-     *last release*: Mar 02, 2021,
+     *last release*: Jul 16, 2022,
      *status*: N/A,
      *requires*: N/A
 
      A hack to explicitly set up and tear down fixtures.
 
   :pypi:`pytest-subtests`
-     *last release*: May 29, 2021,
+     *last release*: Dec 10, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=5.3.0)
+     *requires*: pytest>=7.4
 
      unittest subTest() support and subtests fixture
 
   :pypi:`pytest-subunit`
-     *last release*: Aug 29, 2017,
+     *last release*: Sep 17, 2023,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest (>=2.3)
 
      pytest-subunit is a plugin for py.test which outputs testsresult in subunit format.
 
   :pypi:`pytest-sugar`
-     *last release*: Jul 06, 2020,
-     *status*: 3 - Alpha,
-     *requires*: N/A
+     *last release*: Feb 01, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest >=6.2.0
 
      pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly).
 
-  :pypi:`pytest-sugar-bugfix159`
-     *last release*: Nov 07, 2018,
-     *status*: 5 - Production/Stable,
-     *requires*: pytest (!=3.7.3,>=3.5); extra == 'testing'
+  :pypi:`pytest-suitemanager`
+     *last release*: Apr 28, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
 
-     Workaround for https://github.com/Frozenball/pytest-sugar/issues/159
+     A simple plugin to use with pytest
 
-  :pypi:`pytest-super-check`
-     *last release*: Aug 12, 2021,
-     *status*: 5 - Production/Stable,
-     *requires*: pytest
+  :pypi:`pytest-suite-timeout`
+     *last release*: Jan 26, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
+
+     A pytest plugin for ensuring max suite time
+
+  :pypi:`pytest-supercov`
+     *last release*: Jul 02, 2023,
+     *status*: N/A,
+     *requires*: N/A
 
-     Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc.
+     Pytest plugin for measuring explicit test-file to source-file coverage
 
   :pypi:`pytest-svn`
-     *last release*: May 28, 2019,
+     *last release*: Oct 17, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
@@ -6817,8 +11378,29 @@ This list contains 963 plugins.
 
      pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests.
 
+  :pypi:`pytest-system-statistics`
+     *last release*: Feb 16, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=6.0.0)
+
+     Pytest plugin to track and report system usage statistics
+
+  :pypi:`pytest-system-test-plugin`
+     *last release*: Feb 03, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pyst - Pytest System-Test Plugin
+
+  :pypi:`pytest_tagging`
+     *last release*: Nov 08, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.1.3
+
+     a pytest plugin to tag tests
+
   :pypi:`pytest-takeltest`
-     *last release*: Oct 13, 2021,
+     *last release*: Sep 07, 2024,
      *status*: N/A,
      *requires*: N/A
 
@@ -6831,10 +11413,17 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-tally`
+     *last release*: May 22, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=6.2.5)
+
+     A Pytest plugin to generate realtime summary stats, and display them in-console using a text-based dashboard.
+
   :pypi:`pytest-tap`
-     *last release*: Oct 27, 2021,
+     *last release*: Jan 30, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.0)
+     *requires*: pytest>=3.0
 
      Test Anything Protocol (TAP) reporting plugin for pytest
 
@@ -6852,6 +11441,13 @@ This list contains 963 plugins.
 
      Pytest plugin for remote target orchestration.
 
+  :pypi:`pytest-taskgraph`
+     *last release*: Dec 12, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Add your description here
+
   :pypi:`pytest-tblineinfo`
      *last release*: Dec 01, 2015,
      *status*: 3 - Alpha,
@@ -6859,6 +11455,20 @@ This list contains 963 plugins.
 
      tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used
 
+  :pypi:`pytest-tcpclient`
+     *last release*: Nov 16, 2022,
+     *status*: N/A,
+     *requires*: pytest (<8,>=7.1.3)
+
+     A pytest plugin for testing TCP clients
+
+  :pypi:`pytest-tdd`
+     *last release*: Aug 18, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     run pytest on a python module
+
   :pypi:`pytest-teamcity-logblock`
      *last release*: May 15, 2018,
      *status*: 4 - Beta,
@@ -6866,13 +11476,27 @@ This list contains 963 plugins.
 
      py.test plugin to introduce block structure in teamcity build log, if output is not captured
 
+  :pypi:`pytest-teardown`
+     *last release*: Feb 03, 2025,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=7.4.1
+
+
+
   :pypi:`pytest-telegram`
-     *last release*: Dec 10, 2020,
+     *last release*: Apr 25, 2024,
      *status*: 5 - Production/Stable,
      *requires*: N/A
 
      Pytest to Telegram reporting plugin
 
+  :pypi:`pytest-telegram-notifier`
+     *last release*: Jun 27, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Telegram notification plugin for Pytest
+
   :pypi:`pytest-tempdir`
      *last release*: Oct 11, 2019,
      *status*: 4 - Beta,
@@ -6880,10 +11504,17 @@ This list contains 963 plugins.
 
      Predictable and repeatable tempdir support.
 
+  :pypi:`pytest-terra-fixt`
+     *last release*: Sep 15, 2022,
+     *status*: N/A,
+     *requires*: pytest (==6.2.5)
+
+     Terraform and Terragrunt fixtures for pytest
+
   :pypi:`pytest-terraform`
-     *last release*: Nov 10, 2021,
+     *last release*: May 21, 2024,
      *status*: N/A,
-     *requires*: pytest (>=6.0)
+     *requires*: pytest>=6.0
 
      A pytest plugin for using terraform fixtures
 
@@ -6908,34 +11539,69 @@ This list contains 963 plugins.
 
      Test configuration plugin for pytest.
 
+  :pypi:`pytest-testdata`
+     *last release*: Aug 30, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     Get and load testdata in pytest projects
+
   :pypi:`pytest-testdirectory`
-     *last release*: Nov 06, 2018,
+     *last release*: May 02, 2023,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      A py.test plugin providing temporary directories in unit tests.
 
   :pypi:`pytest-testdox`
-     *last release*: Oct 13, 2020,
+     *last release*: Jul 22, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.7.0)
+     *requires*: pytest (>=4.6.0)
 
      A testdox format reporter for pytest
 
+  :pypi:`pytest-test-grouping`
+     *last release*: Feb 01, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=2.5)
+
+     A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups.
+
   :pypi:`pytest-test-groups`
-     *last release*: Oct 25, 2016,
+     *last release*: Mar 28, 2025,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest>=7.0.0
 
      A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups.
 
   :pypi:`pytest-testinfra`
-     *last release*: Jun 20, 2021,
+     *last release*: Mar 30, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=6
+
+     Test infrastructures
+
+  :pypi:`pytest-testinfra-jpic`
+     *last release*: Sep 21, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     Test infrastructures
+
+  :pypi:`pytest-testinfra-winrm-transport`
+     *last release*: Sep 21, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (!=3.0.2)
+     *requires*: N/A
 
      Test infrastructures
 
+  :pypi:`pytest-testit-parametrize`
+     *last release*: Dec 04, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.3.3
+
+     A pytest plugin for uploading parameterized tests parameters into TMS TestIT
+
   :pypi:`pytest-testlink-adaptor`
      *last release*: Dec 20, 2018,
      *status*: 4 - Beta,
@@ -6944,9 +11610,30 @@ This list contains 963 plugins.
      pytest reporting plugin for testlink
 
   :pypi:`pytest-testmon`
-     *last release*: Oct 22, 2021,
+     *last release*: Dec 22, 2024,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest<9,>=5
+
+     selects tests affected by changed files and methods
+
+  :pypi:`pytest-testmon-dev`
+     *last release*: Mar 30, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (<8,>=5)
+
+     selects tests affected by changed files and methods
+
+  :pypi:`pytest-testmon-oc`
+     *last release*: Jun 01, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (<8,>=5)
+
+     nOly selects tests affected by changed files and methods
+
+  :pypi:`pytest-testmon-skip-libraries`
+     *last release*: Mar 03, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest (<8,>=5)
 
      selects tests affected by changed files and methods
 
@@ -6957,6 +11644,13 @@ This list contains 963 plugins.
 
      Plugin to use TestObject Suites with Pytest
 
+  :pypi:`pytest-testpluggy`
+     *last release*: Jan 07, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     set your encoding
+
   :pypi:`pytest-testrail`
      *last release*: Aug 27, 2020,
      *status*: N/A,
@@ -6965,21 +11659,21 @@ This list contains 963 plugins.
      pytest plugin for creating TestRail runs and adding results
 
   :pypi:`pytest-testrail2`
-     *last release*: Nov 17, 2020,
+     *last release*: Feb 10, 2023,
      *status*: N/A,
-     *requires*: pytest (>=5)
+     *requires*: pytest (<8.0,>=7.2.0)
 
-     A small example package
+     A pytest plugin to upload results to TestRail.
 
   :pypi:`pytest-testrail-api`
-     *last release*: Nov 30, 2021,
+     *last release*: Mar 17, 2025,
      *status*: N/A,
-     *requires*: pytest (>=5.5)
+     *requires*: pytest
 
-     Плагин Pytest, для интеграции с TestRail
+     TestRail Api Python Client
 
   :pypi:`pytest-testrail-api-client`
-     *last release*: Dec 03, 2021,
+     *last release*: Dec 14, 2021,
      *status*: N/A,
      *requires*: pytest
 
@@ -7006,10 +11700,17 @@ This list contains 963 plugins.
 
      pytest plugin for creating TestRail runs and adding results
 
+  :pypi:`pytest-testrail-integrator`
+     *last release*: Aug 01, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=6.2.5)
+
+     Pytest plugin for sending report to testrail system.
+
   :pypi:`pytest-testrail-ns`
-     *last release*: Oct 08, 2021,
+     *last release*: Aug 12, 2022,
      *status*: N/A,
-     *requires*: pytest (>=3.6)
+     *requires*: N/A
 
      pytest plugin for creating TestRail runs and adding results
 
@@ -7027,13 +11728,27 @@ This list contains 963 plugins.
 
 
 
+  :pypi:`pytest-testrail-results`
+     *last release*: Mar 04, 2024,
+     *status*: N/A,
+     *requires*: pytest >=7.2.0
+
+     A pytest plugin to upload results to TestRail.
+
   :pypi:`pytest-testreport`
-     *last release*: Nov 12, 2021,
+     *last release*: Dec 01, 2022,
      *status*: 4 - Beta,
      *requires*: pytest (>=3.5.0)
 
 
 
+  :pypi:`pytest-testreport-new`
+     *last release*: Oct 07, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest >=3.5.0
+
+
+
   :pypi:`pytest-testslide`
      *last release*: Jan 07, 2021,
      *status*: 5 - Production/Stable,
@@ -7048,20 +11763,41 @@ This list contains 963 plugins.
 
      Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply
 
+  :pypi:`pytest-test-tracer-for-pytest`
+     *last release*: Jun 28, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A plugin that allows coll test data for use on Test Tracer
+
+  :pypi:`pytest-test-tracer-for-pytest-bdd`
+     *last release*: Aug 20, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A plugin that allows coll test data for use on Test Tracer
+
   :pypi:`pytest-test-utils`
-     *last release*: Nov 30, 2021,
+     *last release*: Feb 08, 2024,
      *status*: N/A,
-     *requires*: pytest (>=5)
+     *requires*: pytest >=3.9
 
 
 
   :pypi:`pytest-tesults`
-     *last release*: Jul 31, 2021,
+     *last release*: Nov 12, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest>=3.5.0
 
      Tesults plugin for pytest
 
+  :pypi:`pytest-textual-snapshot`
+     *last release*: Jan 23, 2025,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=8.0.0
+
+     Snapshot testing for Textual apps
+
   :pypi:`pytest-tezos`
      *last release*: Jan 16, 2020,
      *status*: 4 - Beta,
@@ -7069,6 +11805,20 @@ This list contains 963 plugins.
 
      pytest-ligo
 
+  :pypi:`pytest-tf`
+     *last release*: May 29, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.2.1
+
+     Test your OpenTofu and Terraform config using a PyTest plugin
+
+  :pypi:`pytest-th2-bdd`
+     *last release*: May 13, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     pytest_th2_bdd
+
   :pypi:`pytest-thawgun`
      *last release*: May 26, 2020,
      *status*: 3 - Alpha,
@@ -7076,10 +11826,17 @@ This list contains 963 plugins.
 
      Pytest plugin for time travel
 
+  :pypi:`pytest-thread`
+     *last release*: Jul 07, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
   :pypi:`pytest-threadleak`
-     *last release*: Sep 08, 2017,
+     *last release*: Jul 03, 2022,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest (>=3.1.1)
 
      Detects thread leaks
 
@@ -7090,6 +11847,20 @@ This list contains 963 plugins.
 
      Ticking on tests
 
+  :pypi:`pytest-time`
+     *last release*: Jan 20, 2025,
+     *status*: 3 - Alpha,
+     *requires*: pytest
+
+
+
+  :pypi:`pytest-timeassert-ethan`
+     *last release*: Dec 25, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     execution duration
+
   :pypi:`pytest-timeit`
      *last release*: Oct 13, 2016,
      *status*: 4 - Beta,
@@ -7098,9 +11869,9 @@ This list contains 963 plugins.
      A pytest plugin to time test function runs
 
   :pypi:`pytest-timeout`
-     *last release*: Oct 11, 2021,
+     *last release*: Mar 07, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.0.0)
+     *requires*: pytest >=7.0.0
 
      pytest plugin to abort hanging tests
 
@@ -7112,35 +11883,56 @@ This list contains 963 plugins.
      Linux-only Pytest plugin to control durations of various test case execution phases
 
   :pypi:`pytest-timer`
-     *last release*: Jun 02, 2021,
+     *last release*: Dec 26, 2023,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest
 
      A timer plugin for pytest
 
   :pypi:`pytest-timestamper`
-     *last release*: Jun 06, 2021,
+     *last release*: Mar 27, 2024,
      *status*: N/A,
      *requires*: N/A
 
      Pytest plugin to add a timestamp prefix to the pytest output
 
-  :pypi:`pytest-tipsi-django`
-     *last release*: Nov 17, 2021,
+  :pypi:`pytest-timestamps`
+     *last release*: Sep 11, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.3,<8.0)
+
+     A simple plugin to view timestamps for each test
+
+  :pypi:`pytest-tiny-api-client`
+     *last release*: Jan 04, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     The companion pytest plugin for tiny-api-client
+
+  :pypi:`pytest-tinybird`
+     *last release*: Feb 18, 2025,
      *status*: 4 - Beta,
-     *requires*: pytest (>=6.0.0)
+     *requires*: pytest>=3.8.0
 
+     A pytest plugin to report test results to tinybird
+
+  :pypi:`pytest-tipsi-django`
+     *last release*: Feb 05, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=6.0.0
 
+     Better fixtures for django
 
   :pypi:`pytest-tipsi-testing`
-     *last release*: Nov 04, 2020,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=3.3.0)
+     *last release*: Feb 04, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest>=3.3.0
 
      Better fixtures management. Various helpers
 
   :pypi:`pytest-tldr`
-     *last release*: Mar 12, 2021,
+     *last release*: Oct 26, 2022,
      *status*: 4 - Beta,
      *requires*: pytest (>=3.5.0)
 
@@ -7153,13 +11945,41 @@ This list contains 963 plugins.
 
      Cloud Jira Test Management (TM4J) PyTest reporter plugin
 
+  :pypi:`pytest-tmnet`
+     *last release*: Mar 01, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     A small example package
+
+  :pypi:`pytest-tmp-files`
+     *last release*: Dec 08, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     Utilities to create temporary file hierarchies in pytest.
+
+  :pypi:`pytest-tmpfs`
+     *last release*: Aug 29, 2022,
+     *status*: N/A,
+     *requires*: pytest
+
+     A pytest plugin that helps you on using a temporary filesystem for testing.
+
   :pypi:`pytest-tmreport`
-     *last release*: Nov 17, 2021,
+     *last release*: Aug 12, 2022,
      *status*: N/A,
      *requires*: N/A
 
      this is a vue-element ui report for pytest
 
+  :pypi:`pytest-tmux`
+     *last release*: Apr 22, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     A pytest plugin that enables tmux driven tests
+
   :pypi:`pytest-todo`
      *last release*: May 23, 2019,
      *status*: 4 - Beta,
@@ -7174,19 +11994,40 @@ This list contains 963 plugins.
 
 
 
-  :pypi:`pytest-toolbelt`
-     *last release*: Aug 12, 2019,
-     *status*: 3 - Alpha,
+  :pypi:`pytest-toolbelt`
+     *last release*: Aug 12, 2019,
+     *status*: 3 - Alpha,
+     *requires*: N/A
+
+     This is just a collection of utilities for pytest, but don't really belong in pytest proper.
+
+  :pypi:`pytest-toolbox`
+     *last release*: Apr 07, 2018,
+     *status*: N/A,
+     *requires*: pytest (>=3.5.0)
+
+     Numerous useful plugins for pytest.
+
+  :pypi:`pytest-toolkit`
+     *last release*: Jun 07, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Useful utils for testing
+
+  :pypi:`pytest-tools`
+     *last release*: Oct 21, 2022,
+     *status*: 4 - Beta,
      *requires*: N/A
 
-     This is just a collection of utilities for pytest, but don't really belong in pytest proper.
+     Pytest tools
 
-  :pypi:`pytest-toolbox`
-     *last release*: Apr 07, 2018,
+  :pypi:`pytest-topo`
+     *last release*: Jun 05, 2024,
      *status*: N/A,
-     *requires*: pytest (>=3.5.0)
+     *requires*: pytest>=7.0.0
 
-     Numerous useful plugins for pytest.
+     Topological sorting for pytest
 
   :pypi:`pytest-tornado`
      *last release*: Jun 17, 2020,
@@ -7216,6 +12057,13 @@ This list contains 963 plugins.
 
      py.test plugin for testing Python 3.5+ Tornado code
 
+  :pypi:`pytest-trace`
+     *last release*: Jun 19, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=4.6)
+
+     Save OpenTelemetry spans generated during testing
+
   :pypi:`pytest-track`
      *last release*: Feb 26, 2021,
      *status*: 3 - Alpha,
@@ -7224,9 +12072,9 @@ This list contains 963 plugins.
 
 
   :pypi:`pytest-translations`
-     *last release*: Nov 05, 2021,
+     *last release*: Sep 11, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: N/A
+     *requires*: pytest (>=7)
 
      Test your translation files.
 
@@ -7259,12 +12107,19 @@ This list contains 963 plugins.
      py.test plugin for using the same _trial_temp working directory as trial
 
   :pypi:`pytest-trio`
-     *last release*: Oct 16, 2020,
+     *last release*: Nov 01, 2022,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest (>=7.2.0)
 
      Pytest plugin for trio
 
+  :pypi:`pytest-trytond`
+     *last release*: Nov 04, 2022,
+     *status*: 4 - Beta,
+     *requires*: pytest (>=5)
+
+     Pytest plugin for the Tryton server framework
+
   :pypi:`pytest-tspwplib`
      *last release*: Jan 08, 2021,
      *status*: 4 - Beta,
@@ -7272,6 +12127,13 @@ This list contains 963 plugins.
 
      A simple plugin to use with tspwplib
 
+  :pypi:`pytest-tst`
+     *last release*: Apr 27, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=5.0.0)
+
+     Customize pytest options, output and exit code to make it compatible with tst
+
   :pypi:`pytest-tstcls`
      *last release*: Mar 23, 2020,
      *status*: 5 - Production/Stable,
@@ -7279,20 +12141,69 @@ This list contains 963 plugins.
 
      Test Class Base
 
+  :pypi:`pytest-tui`
+     *last release*: Dec 08, 2023,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Text User Interface (TUI) and HTML report for Pytest test runs
+
+  :pypi:`pytest-tutorials`
+     *last release*: Mar 11, 2023,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
+  :pypi:`pytest-twilio-conversations-client-mock`
+     *last release*: Aug 02, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+
+
   :pypi:`pytest-twisted`
-     *last release*: Aug 30, 2021,
+     *last release*: Sep 10, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=2.3)
+     *requires*: pytest>=2.3
 
      A twisted plugin for pytest.
 
+  :pypi:`pytest-typechecker`
+     *last release*: Feb 04, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=6.2.5,<7.0.0)
+
+     Run type checkers on specified test files
+
+  :pypi:`pytest-typhoon-config`
+     *last release*: Apr 07, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     A Typhoon HIL plugin that facilitates test parameter configuration at runtime
+
+  :pypi:`pytest-typhoon-polarion`
+     *last release*: Feb 01, 2024,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Typhoontest plugin for Siemens Polarion
+
   :pypi:`pytest-typhoon-xray`
-     *last release*: Nov 03, 2021,
+     *last release*: Aug 15, 2023,
      *status*: 4 - Beta,
      *requires*: N/A
 
      Typhoon HIL plugin for pytest
 
+  :pypi:`pytest-typing-runner`
+     *last release*: Oct 27, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     Pytest plugin to make it easier to run and check python code against static type checkers
+
   :pypi:`pytest-tytest`
      *last release*: May 25, 2020,
      *status*: 4 - Beta,
@@ -7314,6 +12225,34 @@ This list contains 963 plugins.
 
      Text User Interface for running python tests
 
+  :pypi:`pytest-ui-failed-screenshot`
+     *last release*: Dec 06, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     UI自动测试失败时自动截图,并将截图加入到测试报告中
+
+  :pypi:`pytest-ui-failed-screenshot-allure`
+     *last release*: Dec 06, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     UI自动测试失败时自动截图,并将截图加入到Allure测试报告中
+
+  :pypi:`pytest-uncollect-if`
+     *last release*: Dec 26, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     A plugin to uncollect pytests tests rather than using skipif
+
+  :pypi:`pytest-unflakable`
+     *last release*: Apr 30, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=6.2.0
+
+     Unflakable plugin for PyTest
+
   :pypi:`pytest-unhandled-exception-exit-code`
      *last release*: Jun 22, 2020,
      *status*: 5 - Production/Stable,
@@ -7321,6 +12260,13 @@ This list contains 963 plugins.
 
      Plugin for py.test set a different exit code on uncaught exceptions
 
+  :pypi:`pytest-unique`
+     *last release*: Mar 23, 2025,
+     *status*: N/A,
+     *requires*: pytest<8.0.0,>=7.4.2
+
+     Pytest fixture to generate unique values.
+
   :pypi:`pytest-unittest-filter`
      *last release*: Jan 12, 2019,
      *status*: 4 - Beta,
@@ -7328,6 +12274,20 @@ This list contains 963 plugins.
 
      A pytest plugin for filtering unittest-based test classes
 
+  :pypi:`pytest-unittest-id-runner`
+     *last release*: Feb 09, 2025,
+     *status*: N/A,
+     *requires*: pytest>=6.0.0
+
+     A pytest plugin to run tests using unittest-style test IDs
+
+  :pypi:`pytest-unmagic`
+     *last release*: Oct 22, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Pytest fixtures with conventional import semantics
+
   :pypi:`pytest-unmarked`
      *last release*: Aug 27, 2019,
      *status*: 5 - Production/Stable,
@@ -7336,12 +12296,26 @@ This list contains 963 plugins.
      Run only unmarked tests
 
   :pypi:`pytest-unordered`
-     *last release*: Mar 28, 2021,
+     *last release*: Jul 05, 2024,
      *status*: 4 - Beta,
-     *requires*: N/A
+     *requires*: pytest>=7.0.0
 
      Test equality of unordered collections in pytest
 
+  :pypi:`pytest-unstable`
+     *last release*: Sep 27, 2022,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Set a test as unstable to return 0 even if it failed
+
+  :pypi:`pytest-unused-fixtures`
+     *last release*: Mar 15, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>7.3.2
+
+     A pytest plugin to list unused fixtures after a test run.
+
   :pypi:`pytest-upload-report`
      *last release*: Jun 18, 2021,
      *status*: 5 - Production/Stable,
@@ -7350,9 +12324,9 @@ This list contains 963 plugins.
      pytest-upload-report is a plugin for pytest that upload your test report for test results.
 
   :pypi:`pytest-utils`
-     *last release*: Dec 04, 2021,
+     *last release*: Feb 02, 2023,
      *status*: 4 - Beta,
-     *requires*: pytest (>=6.2.5,<7.0.0)
+     *requires*: pytest (>=7.0.0,<8.0.0)
 
      Some helpers for pytest.
 
@@ -7371,14 +12345,14 @@ This list contains 963 plugins.
 
 
   :pypi:`pytest-variables`
-     *last release*: Oct 23, 2019,
+     *last release*: Feb 01, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=2.4.2)
+     *requires*: pytest>=7.0.0
 
      pytest plugin for providing variables to tests/fixtures
 
   :pypi:`pytest-variant`
-     *last release*: Jun 20, 2021,
+     *last release*: Jun 06, 2022,
      *status*: N/A,
      *requires*: N/A
 
@@ -7392,9 +12366,9 @@ This list contains 963 plugins.
      Plugin for managing VCR.py cassettes
 
   :pypi:`pytest-vcr-delete-on-fail`
-     *last release*: Aug 13, 2021,
-     *status*: 4 - Beta,
-     *requires*: pytest (>=6.2.2,<7.0.0)
+     *last release*: Feb 16, 2024,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest (>=8.0.0,<9.0.0)
 
      A pytest plugin that automates vcrpy cassettes deletion on test failure.
 
@@ -7405,22 +12379,22 @@ This list contains 963 plugins.
 
      Test from HTTP interactions to dataframe processed.
 
+  :pypi:`pytest-vcs`
+     *last release*: Sep 22, 2022,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+
+
   :pypi:`pytest-venv`
-     *last release*: Aug 04, 2020,
+     *last release*: Nov 23, 2023,
      *status*: 4 - Beta,
      *requires*: pytest
 
      py.test fixture for creating a virtual environment
 
-  :pypi:`pytest-ver`
-     *last release*: Aug 30, 2021,
-     *status*: 2 - Pre-Alpha,
-     *requires*: N/A
-
-     Pytest module with Verification Report
-
   :pypi:`pytest-verbose-parametrize`
-     *last release*: May 28, 2019,
+     *last release*: Nov 29, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
@@ -7434,12 +12408,26 @@ This list contains 963 plugins.
      A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window.
 
   :pypi:`pytest-virtualenv`
-     *last release*: May 28, 2019,
+     *last release*: Nov 29, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      Virtualenv fixture for py.test
 
+  :pypi:`pytest-visual`
+     *last release*: Nov 28, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.0.0
+
+
+
+  :pypi:`pytest-vnc`
+     *last release*: Nov 06, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     VNC client for Pytest
+
   :pypi:`pytest-voluptuous`
      *last release*: Jun 09, 2020,
      *status*: N/A,
@@ -7454,6 +12442,20 @@ This list contains 963 plugins.
 
      A pytest plugin to easily enable debugging tests within Visual Studio Code
 
+  :pypi:`pytest-vscode-pycharm-cls`
+     *last release*: Feb 01, 2023,
+     *status*: N/A,
+     *requires*: pytest
+
+     A PyTest helper to enable start remote debugger on test start or failure or when pytest.set_trace is used.
+
+  :pypi:`pytest-vtestify`
+     *last release*: Feb 04, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     A pytest plugin for visual assertion using SSIM and image comparison.
+
   :pypi:`pytest-vts`
      *last release*: Jun 05, 2019,
      *status*: N/A,
@@ -7461,6 +12463,13 @@ This list contains 963 plugins.
 
      pytest plugin for automatic recording of http stubbed tests
 
+  :pypi:`pytest-vulture`
+     *last release*: Nov 25, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
+
+     A pytest plugin to checks dead code with vulture
+
   :pypi:`pytest-vw`
      *last release*: Oct 07, 2015,
      *status*: 4 - Beta,
@@ -7482,6 +12491,13 @@ This list contains 963 plugins.
 
      Pytest plugin for testing whatsapp bots with end to end tests
 
+  :pypi:`pytest-wake`
+     *last release*: Nov 19, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+
+
   :pypi:`pytest-watch`
      *last release*: May 20, 2018,
      *status*: N/A,
@@ -7490,11 +12506,25 @@ This list contains 963 plugins.
      Local continuous test runner with pytest and watchdog.
 
   :pypi:`pytest-watcher`
-     *last release*: Sep 18, 2021,
-     *status*: 3 - Alpha,
+     *last release*: Aug 28, 2024,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     Automatically rerun your tests on file modifications
+
+  :pypi:`pytest-watch-plugin`
+     *last release*: Sep 12, 2024,
+     *status*: N/A,
      *requires*: N/A
 
-     Continiously runs pytest on changes in \*.py files
+     Placeholder for internal package
+
+  :pypi:`pytest_wdb`
+     *last release*: Jul 04, 2016,
+     *status*: N/A,
+     *requires*: N/A
+
+     Trace pytest tests with wdb to halt on error with --wdb.
 
   :pypi:`pytest-wdl`
      *last release*: Nov 17, 2020,
@@ -7503,13 +12533,34 @@ This list contains 963 plugins.
 
      Pytest plugin for testing WDL workflows.
 
+  :pypi:`pytest-web3-data`
+     *last release*: Oct 04, 2023,
+     *status*: 4 - Beta,
+     *requires*: pytest
+
+     A pytest plugin to fetch test data from IPFS HTTP gateways during pytest execution.
+
   :pypi:`pytest-webdriver`
-     *last release*: May 28, 2019,
+     *last release*: Oct 17, 2024,
      *status*: 5 - Production/Stable,
      *requires*: pytest
 
      Selenium webdriver fixture for py.test
 
+  :pypi:`pytest-webstage`
+     *last release*: Sep 20, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0,>=7.0
+
+     Test web apps with pytest
+
+  :pypi:`pytest-webtest-extras`
+     *last release*: Dec 28, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.0.0
+
+     Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources.
+
   :pypi:`pytest-wetest`
      *last release*: Nov 10, 2018,
      *status*: 4 - Beta,
@@ -7517,6 +12568,13 @@ This list contains 963 plugins.
 
      Welian API Automation test framework pytest plugin
 
+  :pypi:`pytest-when`
+     *last release*: Nov 29, 2024,
+     *status*: N/A,
+     *requires*: pytest>=7.3.1
+
+     Utility which makes mocking more readable and controllable
+
   :pypi:`pytest-whirlwind`
      *last release*: Jun 12, 2020,
      *status*: N/A,
@@ -7545,6 +12603,20 @@ This list contains 963 plugins.
 
      Windows tray notifications for py.test results.
 
+  :pypi:`pytest-wiremock`
+     *last release*: Mar 27, 2022,
+     *status*: N/A,
+     *requires*: pytest (>=7.1.1,<8.0.0)
+
+     A pytest plugin for programmatically using wiremock in integration tests
+
+  :pypi:`pytest-wiretap`
+     *last release*: Mar 18, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     \`pytest\` plugin for recording call stacks
+
   :pypi:`pytest-with-docker`
      *last release*: Nov 09, 2021,
      *status*: N/A,
@@ -7552,19 +12624,26 @@ This list contains 963 plugins.
 
      pytest with docker helpers.
 
+  :pypi:`pytest-workaround-12888`
+     *last release*: Jan 15, 2025,
+     *status*: N/A,
+     *requires*: N/A
+
+     forces an import of readline early in the process to work around pytest bug #12888
+
   :pypi:`pytest-workflow`
-     *last release*: Dec 03, 2021,
+     *last release*: Mar 18, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.4.0)
+     *requires*: pytest >=7.0.0
 
      A pytest plugin for configuring workflow/pipeline tests using YAML files
 
   :pypi:`pytest-xdist`
-     *last release*: Sep 21, 2021,
+     *last release*: Apr 28, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=6.0.0)
+     *requires*: pytest>=7.0.0
 
-     pytest xdist plugin for distributed testing and loop-on-failing modes
+     pytest xdist plugin for distributed testing, most importantly across multiple CPUs
 
   :pypi:`pytest-xdist-debug-for-graingert`
      *last release*: Jul 24, 2019,
@@ -7587,6 +12666,20 @@ This list contains 963 plugins.
 
      pytest plugin helps to reproduce failures for particular xdist node
 
+  :pypi:`pytest-xdist-worker-stats`
+     *last release*: Mar 15, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.0.0
+
+     A pytest plugin to list worker statistics after a xdist run.
+
+  :pypi:`pytest-xdocker`
+     *last release*: Mar 23, 2025,
+     *status*: N/A,
+     *requires*: pytest<8.0.0,>=7.4.2
+
+     Pytest fixture to run docker across test runs.
+
   :pypi:`pytest-xfaillist`
      *last release*: Sep 17, 2021,
      *status*: N/A,
@@ -7601,6 +12694,20 @@ This list contains 963 plugins.
 
      Pytest fixtures providing data read from function, module or package related (x)files.
 
+  :pypi:`pytest-xflaky`
+     *last release*: Oct 14, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.2.1
+
+     A simple plugin to use with pytest
+
+  :pypi:`pytest-xiuyu`
+     *last release*: Jul 25, 2023,
+     *status*: 5 - Production/Stable,
+     *requires*: N/A
+
+     This is a pytest plugin
+
   :pypi:`pytest-xlog`
      *last release*: May 31, 2020,
      *status*: 4 - Beta,
@@ -7608,17 +12715,31 @@ This list contains 963 plugins.
 
      Extended logging for test and decorators
 
+  :pypi:`pytest-xlsx`
+     *last release*: Aug 07, 2024,
+     *status*: N/A,
+     *requires*: pytest~=8.2.2
+
+     pytest plugin for generating test cases by xlsx(excel)
+
+  :pypi:`pytest-xml`
+     *last release*: Nov 14, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.0.0
+
+     Create simple XML results for parsing
+
   :pypi:`pytest-xpara`
-     *last release*: Oct 30, 2017,
+     *last release*: Aug 07, 2024,
      *status*: 3 - Alpha,
      *requires*: pytest
 
      An extended parametrizing plugin of pytest.
 
   :pypi:`pytest-xprocess`
-     *last release*: Jul 28, 2021,
+     *last release*: May 19, 2024,
      *status*: 4 - Beta,
-     *requires*: pytest (>=2.8)
+     *requires*: pytest>=2.8
 
      A pytest plugin for managing processes across test runs.
 
@@ -7637,18 +12758,39 @@ This list contains 963 plugins.
 
 
   :pypi:`pytest-xray-server`
-     *last release*: Oct 27, 2021,
+     *last release*: May 03, 2022,
      *status*: 3 - Alpha,
      *requires*: pytest (>=5.3.1)
 
 
 
+  :pypi:`pytest-xskynet`
+     *last release*: Feb 20, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     A package to prevent Dependency Confusion attacks against Yandex.
+
+  :pypi:`pytest-xstress`
+     *last release*: Jun 01, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.0.0
+
+
+
   :pypi:`pytest-xvfb`
-     *last release*: Jun 09, 2020,
+     *last release*: Mar 12, 2025,
      *status*: 4 - Beta,
-     *requires*: pytest (>=2.8.1)
+     *requires*: pytest>=2.8.1
 
-     A pytest plugin to run Xvfb for tests.
+     A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests.
+
+  :pypi:`pytest-xvirt`
+     *last release*: Dec 15, 2024,
+     *status*: 4 - Beta,
+     *requires*: pytest>=7.2.2
+
+     A pytest plugin to virtualize test. For example to transparently running them on a remote box.
 
   :pypi:`pytest-yaml`
      *last release*: Oct 05, 2018,
@@ -7657,6 +12799,20 @@ This list contains 963 plugins.
 
      This plugin is used to load yaml output to your test using pytest framework.
 
+  :pypi:`pytest-yaml-fei`
+     *last release*: Feb 09, 2025,
+     *status*: N/A,
+     *requires*: pytest
+
+     a pytest yaml allure package
+
+  :pypi:`pytest-yaml-sanmu`
+     *last release*: Jan 03, 2025,
+     *status*: N/A,
+     *requires*: pytest>=8.2.2
+
+     Pytest plugin for generating test cases with YAML. In test cases, you can use markers, fixtures, variables, and even call Python functions.
+
   :pypi:`pytest-yamltree`
      *last release*: Mar 02, 2020,
      *status*: 4 - Beta,
@@ -7671,6 +12827,13 @@ This list contains 963 plugins.
 
      Run tests against wsgi apps defined in yaml
 
+  :pypi:`pytest-yaml-yoyo`
+     *last release*: Jun 19, 2023,
+     *status*: N/A,
+     *requires*: pytest (>=7.2.0)
+
+     http/https API run by yaml
+
   :pypi:`pytest-yapf`
      *last release*: Jul 06, 2017,
      *status*: 4 - Beta,
@@ -7679,9 +12842,9 @@ This list contains 963 plugins.
      Run yapf
 
   :pypi:`pytest-yapf3`
-     *last release*: Aug 03, 2020,
+     *last release*: Mar 29, 2023,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=5.4)
+     *requires*: pytest (>=7)
 
      Validate your Python file format with yapf
 
@@ -7692,10 +12855,24 @@ This list contains 963 plugins.
 
      PyTest plugin to run tests concurrently, each \`yield\` switch context to other one
 
+  :pypi:`pytest-yls`
+     *last release*: Oct 18, 2024,
+     *status*: N/A,
+     *requires*: pytest<9.0.0,>=8.3.3
+
+     Pytest plugin to test the YLS as a whole.
+
+  :pypi:`pytest-youqu-playwright`
+     *last release*: Jun 12, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     pytest-youqu-playwright
+
   :pypi:`pytest-yuk`
      *last release*: Mar 26, 2021,
      *status*: N/A,
-     *requires*: N/A
+     *requires*: pytest>=5.0.0
 
      Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk.
 
@@ -7713,16 +12890,79 @@ This list contains 963 plugins.
 
      OWASP ZAP plugin for py.test.
 
+  :pypi:`pytest-zcc`
+     *last release*: Jun 02, 2024,
+     *status*: N/A,
+     *requires*: N/A
+
+     eee
+
   :pypi:`pytest-zebrunner`
-     *last release*: Dec 02, 2021,
+     *last release*: Jul 04, 2024,
      *status*: 5 - Production/Stable,
-     *requires*: pytest (>=4.5.0)
+     *requires*: pytest>=4.5.0
 
      Pytest connector for Zebrunner reporting
 
+  :pypi:`pytest-zeebe`
+     *last release*: Feb 01, 2024,
+     *status*: N/A,
+     *requires*: pytest (>=7.4.2,<8.0.0)
+
+     Pytest fixtures for testing Camunda 8 processes using a Zeebe test engine.
+
+  :pypi:`pytest-zephyr-scale-integration`
+     *last release*: Oct 15, 2024,
+     *status*: N/A,
+     *requires*: pytest
+
+     A library for integrating Jira Zephyr Scale (Adaptavist\TM4J) with pytest
+
+  :pypi:`pytest-zephyr-telegram`
+     *last release*: Sep 30, 2024,
+     *status*: N/A,
+     *requires*: pytest==8.3.2
+
+     Плагин для отправки данных автотестов в Телеграм и Зефир
+
+  :pypi:`pytest-zest`
+     *last release*: Nov 17, 2022,
+     *status*: N/A,
+     *requires*: N/A
+
+     Zesty additions to pytest.
+
+  :pypi:`pytest-zhongwen-wendang`
+     *last release*: Mar 04, 2024,
+     *status*: 4 - Beta,
+     *requires*: N/A
+
+     PyTest 中文文档
+
   :pypi:`pytest-zigzag`
      *last release*: Feb 27, 2019,
      *status*: 4 - Beta,
      *requires*: pytest (~=3.6)
 
      Extend py.test for RPC OpenStack testing.
+
+  :pypi:`pytest-zulip`
+     *last release*: May 07, 2022,
+     *status*: 5 - Production/Stable,
+     *requires*: pytest
+
+     Pytest report plugin for Zulip
+
+  :pypi:`pytest-zy`
+     *last release*: Mar 24, 2024,
+     *status*: N/A,
+     *requires*: pytest~=7.2.0
+
+     接口自动化测试框架
+
+  :pypi:`tursu`
+     *last release*: Apr 05, 2025,
+     *status*: 4 - Beta,
+     *requires*: pytest>=8.3.5
+
+     🎬 A pytest plugin that transpiles Gherkin feature files to Python using AST, enforcing typing for ease of use and debugging.
diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst
index 42897e3d18f..d3dd14a8681 100644
--- a/doc/en/reference/reference.rst
+++ b/doc/en/reference/reference.rst
@@ -1,3 +1,5 @@
+:tocdepth: 3
+
 .. _`api-reference`:
 
 API Reference
@@ -5,9 +7,49 @@ API Reference
 
 This page contains the full reference to pytest's API.
 
-.. contents::
-    :depth: 3
-    :local:
+
+Constants
+---------
+
+pytest.__version__
+~~~~~~~~~~~~~~~~~~
+
+The current pytest version, as a string::
+
+    >>> import pytest
+    >>> pytest.__version__
+    '7.0.0'
+
+.. _`hidden-param`:
+
+pytest.HIDDEN_PARAM
+~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 8.4
+
+Can be passed to ``ids`` of :py:func:`Metafunc.parametrize <pytest.Metafunc.parametrize>`
+or to ``id`` of :func:`pytest.param` to hide a parameter set from the test name.
+Can only be used at most 1 time, as test names need to be unique.
+
+.. _`version-tuple`:
+
+pytest.version_tuple
+~~~~~~~~~~~~~~~~~~~~
+
+.. versionadded:: 7.0
+
+The current pytest version, as a tuple::
+
+    >>> import pytest
+    >>> pytest.version_tuple
+    (7, 0, 0)
+
+For pre-releases, the last component will be a string with the prerelease version::
+
+    >>> import pytest
+    >>> pytest.version_tuple
+    (7, 0, '0rc1')
+
 
 Functions
 ---------
@@ -22,12 +64,20 @@ pytest.fail
 
 **Tutorial**: :ref:`skipping`
 
-.. autofunction:: pytest.fail(reason, [pytrace=True, msg=None])
+.. autofunction:: pytest.fail(reason, [pytrace=True])
+
+.. class:: pytest.fail.Exception
+
+    The exception raised by :func:`pytest.fail`.
 
 pytest.skip
 ~~~~~~~~~~~
 
-.. autofunction:: pytest.skip(reason, [allow_module_level=False, msg=None])
+.. autofunction:: pytest.skip(reason, [allow_module_level=False])
+
+.. class:: pytest.skip.Exception
+
+    The exception raised by :func:`pytest.skip`.
 
 .. _`pytest.importorskip ref`:
 
@@ -41,14 +91,24 @@ pytest.xfail
 
 .. autofunction:: pytest.xfail
 
+.. class:: pytest.xfail.Exception
+
+    The exception raised by :func:`pytest.xfail`.
+
 pytest.exit
 ~~~~~~~~~~~
 
-.. autofunction:: pytest.exit(reason, [returncode=False, msg=None])
+.. autofunction:: pytest.exit(reason, [returncode=None])
+
+.. class:: pytest.exit.Exception
+
+    The exception raised by :func:`pytest.exit`.
 
 pytest.main
 ~~~~~~~~~~~
 
+**Tutorial**: :ref:`pytest.main-usage`
+
 .. autofunction:: pytest.main
 
 pytest.param
@@ -59,7 +119,7 @@ pytest.param
 pytest.raises
 ~~~~~~~~~~~~~
 
-**Tutorial**: :ref:`assertraises`.
+**Tutorial**: :ref:`assertraises`
 
 .. autofunction:: pytest.raises(expected_exception: Exception [, *, match])
     :with: excinfo
@@ -67,15 +127,15 @@ pytest.raises
 pytest.deprecated_call
 ~~~~~~~~~~~~~~~~~~~~~~
 
-**Tutorial**: :ref:`ensuring_function_triggers`.
+**Tutorial**: :ref:`ensuring_function_triggers`
 
-.. autofunction:: pytest.deprecated_call()
+.. autofunction:: pytest.deprecated_call([match])
     :with:
 
 pytest.register_assert_rewrite
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-**Tutorial**: :ref:`assertion-rewriting`.
+**Tutorial**: :ref:`assertion-rewriting`
 
 .. autofunction:: pytest.register_assert_rewrite
 
@@ -90,7 +150,7 @@ pytest.warns
 pytest.freeze_includes
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-**Tutorial**: :ref:`freezing-pytest`.
+**Tutorial**: :ref:`freezing-pytest`
 
 .. autofunction:: pytest.freeze_includes
 
@@ -99,7 +159,7 @@ pytest.freeze_includes
 Marks
 -----
 
-Marks can be used apply meta data to *test functions* (but not fixtures), which can then be accessed by
+Marks can be used to apply metadata to *test functions* (but not fixtures), which can then be accessed by
 fixtures or plugins.
 
 
@@ -110,7 +170,7 @@ fixtures or plugins.
 pytest.mark.filterwarnings
 ~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-**Tutorial**: :ref:`filterwarnings`.
+**Tutorial**: :ref:`filterwarnings`
 
 Add warning filters to marked test items.
 
@@ -127,8 +187,7 @@ Add warning filters to marked test items.
         .. code-block:: python
 
             @pytest.mark.filterwarnings("ignore:.*usage will be deprecated.*:DeprecationWarning")
-            def test_foo():
-                ...
+            def test_foo(): ...
 
 
 .. _`pytest.mark.parametrize ref`:
@@ -136,7 +195,7 @@ Add warning filters to marked test items.
 pytest.mark.parametrize
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-:ref:`parametrize`.
+**Tutorial**: :ref:`parametrize`
 
 This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see there.
 
@@ -146,7 +205,7 @@ This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see
 pytest.mark.skip
 ~~~~~~~~~~~~~~~~
 
-:ref:`skip`.
+**Tutorial**: :ref:`skip`
 
 Unconditionally skip a test function.
 
@@ -160,7 +219,7 @@ Unconditionally skip a test function.
 pytest.mark.skipif
 ~~~~~~~~~~~~~~~~~~
 
-:ref:`skipif`.
+**Tutorial**: :ref:`skipif`
 
 Skip a test function if a condition is ``True``.
 
@@ -176,7 +235,7 @@ Skip a test function if a condition is ``True``.
 pytest.mark.usefixtures
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-**Tutorial**: :ref:`usefixtures`.
+**Tutorial**: :ref:`usefixtures`
 
 Mark a test function as using the given fixture names.
 
@@ -198,26 +257,28 @@ Mark a test function as using the given fixture names.
 pytest.mark.xfail
 ~~~~~~~~~~~~~~~~~~
 
-**Tutorial**: :ref:`xfail`.
+**Tutorial**: :ref:`xfail`
 
 Marks a test function as *expected to fail*.
 
-.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False)
+.. py:function:: pytest.mark.xfail(condition=False, *, reason=None, raises=None, run=True, strict=xfail_strict)
 
-    :type condition: bool or str
-    :param condition:
+    :keyword Union[bool, str] condition:
         Condition for marking the test function as xfail (``True/False`` or a
-        :ref:`condition string <string conditions>`). If a bool, you also have
+        :ref:`condition string <string conditions>`). If a ``bool``, you also have
         to specify ``reason`` (see :ref:`condition string <string conditions>`).
     :keyword str reason:
         Reason why the test function is marked as xfail.
-    :keyword Type[Exception] raises:
-        Exception subclass expected to be raised by the test function; other exceptions will fail the test.
+    :keyword raises:
+        Exception class (or tuple of classes) expected to be raised by the test function; other exceptions will fail the test.
+        Note that subclasses of the classes passed will also result in a match (similar to how the ``except`` statement works).
+    :type raises: Type[:py:exc:`Exception`]
+
     :keyword bool run:
-        If the test function should actually be executed. If ``False``, the function will always xfail and will
+        Whether the test function should actually be executed. If ``False``, the function will always xfail and will
         not be executed (useful if a function is segfaulting).
     :keyword bool strict:
-        * If ``False`` (the default) the function will be shown in the terminal output as ``xfailed`` if it fails
+        * If ``False`` the function will be shown in the terminal output as ``xfailed`` if it fails
           and as ``xpass`` if it passes. In both cases this will not cause the test suite to fail as a whole. This
           is particularly useful to mark *flaky* tests (tests that fail at random) to be tackled later.
         * If ``True``, the function will be shown in the terminal output as ``xfailed`` if it fails, but if it
@@ -225,36 +286,7 @@ Marks a test function as *expected to fail*.
           that are always failing and there should be a clear indication if they unexpectedly start to pass (for example
           a new release of a library fixes a known bug).
 
-
-pytest.__version__
-~~~~~~~~~~~~~~~~~~
-
-The current pytest version, as a string::
-
-    >>> import pytest
-    >>> pytest.__version__
-    '7.0.0'
-
-
-.. _`version-tuple`:
-
-pytest.version_tuple
-~~~~~~~~~~~~~~~~~~~~
-
-.. versionadded:: 7.0
-
-The current pytest version, as a tuple::
-
-    >>> import pytest
-    >>> pytest.version_tuple
-    (7, 0, 0)
-
-For pre-releases, the last component will be a string with the prerelease version::
-
-    >>> import pytest
-    >>> pytest.version_tuple
-    (7, 0, '0rc1')
-
+        Defaults to :confval:`xfail_strict`, which is ``False`` by default.
 
 
 Custom marks
@@ -267,8 +299,7 @@ For example:
 .. code-block:: python
 
     @pytest.mark.timeout(10, "slow", method="thread")
-    def test_function():
-        ...
+    def test_function(): ...
 
 Will create and attach a :class:`Mark <pytest.Mark>` object to the collected
 :class:`Item <pytest.Item>`, which can then be accessed by fixtures or hooks with
@@ -285,17 +316,16 @@ Example for using multiple custom markers:
 
     @pytest.mark.timeout(10, "slow", method="thread")
     @pytest.mark.slow
-    def test_function():
-        ...
+    def test_function(): ...
 
-When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers_with_node>` is used with multiple markers, the marker closest to the function will be iterated over first. The above example will result in ``@pytest.mark.slow`` followed by ``@pytest.mark.timeout(...)``.
+When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers_with_node <_pytest.nodes.Node.iter_markers_with_node>` is used with multiple markers, the marker closest to the function will be iterated over first. The above example will result in ``@pytest.mark.slow`` followed by ``@pytest.mark.timeout(...)``.
 
 .. _`fixtures-api`:
 
 Fixtures
 --------
 
-**Tutorial**: :ref:`fixture`.
+**Tutorial**: :ref:`fixture`
 
 Fixtures are requested by test functions or other fixtures by declaring them as argument names.
 
@@ -331,192 +361,106 @@ For more details, consult the full :ref:`fixtures docs <fixture>`.
     :decorator:
 
 
-.. fixture:: cache
-
-config.cache
-~~~~~~~~~~~~
-
-**Tutorial**: :ref:`cache`.
-
-The ``config.cache`` object allows other plugins and fixtures
-to store and retrieve values across test runs. To access it from fixtures
-request ``pytestconfig`` into your fixture and get it with ``pytestconfig.cache``.
-
-Under the hood, the cache plugin uses the simple
-``dumps``/``loads`` API of the :py:mod:`json` stdlib module.
-
-``config.cache`` is an instance of :class:`pytest.Cache`:
-
-.. autoclass:: pytest.Cache()
-   :members:
-
-
-.. fixture:: capsys
-
-capsys
-~~~~~~
-
-:ref:`captures`.
-
-.. autofunction:: _pytest.capture.capsys()
-    :no-auto-options:
-
-    Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
-
-    Example:
-
-    .. code-block:: python
-
-        def test_output(capsys):
-            print("hello")
-            captured = capsys.readouterr()
-            assert captured.out == "hello\n"
-
-.. autoclass:: pytest.CaptureFixture()
-    :members:
-
-
-.. fixture:: capsysbinary
-
-capsysbinary
-~~~~~~~~~~~~
-
-:ref:`captures`.
-
-.. autofunction:: _pytest.capture.capsysbinary()
-    :no-auto-options:
-
-    Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
-
-    Example:
-
-    .. code-block:: python
-
-        def test_output(capsysbinary):
-            print("hello")
-            captured = capsysbinary.readouterr()
-            assert captured.out == b"hello\n"
-
-
 .. fixture:: capfd
 
 capfd
 ~~~~~~
 
-:ref:`captures`.
+**Tutorial**: :ref:`captures`
 
 .. autofunction:: _pytest.capture.capfd()
     :no-auto-options:
 
-    Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
-
-    Example:
-
-    .. code-block:: python
-
-        def test_system_echo(capfd):
-            os.system('echo "hello"')
-            captured = capfd.readouterr()
-            assert captured.out == "hello\n"
-
 
 .. fixture:: capfdbinary
 
 capfdbinary
 ~~~~~~~~~~~~
 
-:ref:`captures`.
+**Tutorial**: :ref:`captures`
 
 .. autofunction:: _pytest.capture.capfdbinary()
     :no-auto-options:
 
-    Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
-
-    Example:
-
-    .. code-block:: python
-
-        def test_system_echo(capfdbinary):
-            os.system('echo "hello"')
-            captured = capfdbinary.readouterr()
-            assert captured.out == b"hello\n"
 
+.. fixture:: caplog
 
-.. fixture:: doctest_namespace
-
-doctest_namespace
-~~~~~~~~~~~~~~~~~
-
-:ref:`doctest`.
-
-.. autofunction:: _pytest.doctest.doctest_namespace()
+caplog
+~~~~~~
 
-    Usually this fixture is used in conjunction with another ``autouse`` fixture:
+**Tutorial**: :ref:`logging`
 
-    .. code-block:: python
+.. autofunction:: _pytest.logging.caplog()
+    :no-auto-options:
 
-        @pytest.fixture(autouse=True)
-        def add_np(doctest_namespace):
-            doctest_namespace["np"] = numpy
+    Returns a :class:`pytest.LogCaptureFixture` instance.
 
-    For more details: :ref:`doctest_namespace`.
+.. autoclass:: pytest.LogCaptureFixture()
+    :members:
 
 
-.. fixture:: request
+.. fixture:: capsys
 
-request
-~~~~~~~
+capsys
+~~~~~~
 
-:ref:`request example`.
+**Tutorial**: :ref:`captures`
 
-The ``request`` fixture is a special fixture providing information of the requesting test function.
+.. autofunction:: _pytest.capture.capsys()
+    :no-auto-options:
 
-.. autoclass:: pytest.FixtureRequest()
+.. autoclass:: pytest.CaptureFixture()
     :members:
 
+.. fixture:: capteesys
 
-.. fixture:: pytestconfig
+capteesys
+~~~~~~~~~
 
-pytestconfig
-~~~~~~~~~~~~
+**Tutorial**: :ref:`captures`
 
-.. autofunction:: _pytest.fixtures.pytestconfig()
+.. autofunction:: _pytest.capture.capteesys()
+    :no-auto-options:
 
+.. fixture:: capsysbinary
 
-.. fixture:: record_property
+capsysbinary
+~~~~~~~~~~~~
 
-record_property
-~~~~~~~~~~~~~~~~~~~
+**Tutorial**: :ref:`captures`
 
-**Tutorial**: :ref:`record_property example`.
+.. autofunction:: _pytest.capture.capsysbinary()
+    :no-auto-options:
 
-.. autofunction:: _pytest.junitxml.record_property()
 
+.. fixture:: cache
 
-.. fixture:: record_testsuite_property
+config.cache
+~~~~~~~~~~~~
 
-record_testsuite_property
-~~~~~~~~~~~~~~~~~~~~~~~~~
+**Tutorial**: :ref:`cache`
 
-**Tutorial**: :ref:`record_testsuite_property example`.
+The ``config.cache`` object allows other plugins and fixtures
+to store and retrieve values across test runs. To access it from fixtures
+request ``pytestconfig`` into your fixture and get it with ``pytestconfig.cache``.
 
-.. autofunction:: _pytest.junitxml.record_testsuite_property()
+Under the hood, the cache plugin uses the simple
+``dumps``/``loads`` API of the :py:mod:`json` stdlib module.
 
+``config.cache`` is an instance of :class:`pytest.Cache`:
 
-.. fixture:: caplog
+.. autoclass:: pytest.Cache()
+   :members:
 
-caplog
-~~~~~~
 
-:ref:`logging`.
+.. fixture:: doctest_namespace
 
-.. autofunction:: _pytest.logging.caplog()
-    :no-auto-options:
+doctest_namespace
+~~~~~~~~~~~~~~~~~
 
-    Returns a :class:`pytest.LogCaptureFixture` instance.
+**Tutorial**: :ref:`doctest`
 
-.. autoclass:: pytest.LogCaptureFixture()
-    :members:
+.. autofunction:: _pytest.doctest.doctest_namespace()
 
 
 .. fixture:: monkeypatch
@@ -524,7 +468,7 @@ caplog
 monkeypatch
 ~~~~~~~~~~~
 
-:ref:`monkeypatching`.
+**Tutorial**: :ref:`monkeypatching`
 
 .. autofunction:: _pytest.monkeypatch.monkeypatch()
     :no-auto-options:
@@ -535,6 +479,14 @@ monkeypatch
     :members:
 
 
+.. fixture:: pytestconfig
+
+pytestconfig
+~~~~~~~~~~~~
+
+.. autofunction:: _pytest.fixtures.pytestconfig()
+
+
 .. fixture:: pytester
 
 pytester
@@ -571,18 +523,25 @@ To use it, include in your topmost ``conftest.py`` file:
 .. autoclass:: pytest.RecordedHookCall()
     :members:
 
-.. fixture:: testdir
 
-testdir
-~~~~~~~
+.. fixture:: record_property
 
-Identical to :fixture:`pytester`, but provides an instance whose methods return
-legacy ``py.path.local`` objects instead when applicable.
+record_property
+~~~~~~~~~~~~~~~~~~~
 
-New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
+**Tutorial**: :ref:`record_property example`
 
-.. autoclass:: pytest.Testdir()
-    :members:
+.. autofunction:: _pytest.junitxml.record_property()
+
+
+.. fixture:: record_testsuite_property
+
+record_testsuite_property
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+**Tutorial**: :ref:`record_testsuite_property example`
+
+.. autofunction:: _pytest.junitxml.record_testsuite_property()
 
 
 .. fixture:: recwarn
@@ -590,19 +549,42 @@ New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
 recwarn
 ~~~~~~~
 
-**Tutorial**: :ref:`assertwarnings`
+**Tutorial**: :ref:`recwarn`
 
 .. autofunction:: _pytest.recwarn.recwarn()
     :no-auto-options:
 
 .. autoclass:: pytest.WarningsRecorder()
     :members:
+    :special-members: __getitem__, __iter__, __len__
 
-Each recorded warning is an instance of :class:`warnings.WarningMessage`.
 
-.. note::
-    ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
-    differently; see :ref:`ensuring_function_triggers`.
+.. fixture:: request
+
+request
+~~~~~~~
+
+**Example**: :ref:`request example`
+
+The ``request`` fixture is a special fixture providing information of the requesting test function.
+
+.. autoclass:: pytest.FixtureRequest()
+    :members:
+
+
+.. fixture:: testdir
+
+testdir
+~~~~~~~
+
+Identical to :fixture:`pytester`, but provides an instance whose methods return
+legacy ``py.path.local`` objects instead when applicable.
+
+New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
+
+.. autoclass:: pytest.Testdir()
+    :members:
+    :noindex: TimeoutExpired
 
 
 .. fixture:: tmp_path
@@ -610,7 +592,7 @@ Each recorded warning is an instance of :class:`warnings.WarningMessage`.
 tmp_path
 ~~~~~~~~
 
-:ref:`tmp_path`
+**Tutorial**: :ref:`tmp_path`
 
 .. autofunction:: _pytest.tmpdir.tmp_path()
     :no-auto-options:
@@ -621,7 +603,7 @@ tmp_path
 tmp_path_factory
 ~~~~~~~~~~~~~~~~
 
-:ref:`tmp_path_factory example`
+**Tutorial**: :ref:`tmp_path_factory example`
 
 .. _`tmp_path_factory factory api`:
 
@@ -636,7 +618,7 @@ tmp_path_factory
 tmpdir
 ~~~~~~
 
-:ref:`tmpdir and tmpdir_factory`
+**Tutorial**: :ref:`tmpdir and tmpdir_factory`
 
 .. autofunction:: _pytest.legacypath.LegacyTmpdirPlugin.tmpdir()
     :no-auto-options:
@@ -647,7 +629,7 @@ tmpdir
 tmpdir_factory
 ~~~~~~~~~~~~~~
 
-:ref:`tmpdir and tmpdir_factory`
+**Tutorial**: :ref:`tmpdir and tmpdir_factory`
 
 ``tmpdir_factory`` is an instance of :class:`~pytest.TempdirFactory`:
 
@@ -660,20 +642,42 @@ tmpdir_factory
 Hooks
 -----
 
-:ref:`writing-plugins`.
-
-.. currentmodule:: _pytest.hookspec
+**Tutorial**: :ref:`writing-plugins`
 
 Reference to all hooks which can be implemented by :ref:`conftest.py files <localplugin>` and :ref:`plugins <plugins>`.
 
+@pytest.hookimpl
+~~~~~~~~~~~~~~~~
+
+.. function:: pytest.hookimpl
+    :decorator:
+
+    pytest's decorator for marking functions as hook implementations.
+
+    See :ref:`writinghooks` and :func:`pluggy.HookimplMarker`.
+
+@pytest.hookspec
+~~~~~~~~~~~~~~~~
+
+.. function:: pytest.hookspec
+    :decorator:
+
+    pytest's decorator for marking functions as hook specifications.
+
+    See :ref:`declaringhooks` and :func:`pluggy.HookspecMarker`.
+
+.. currentmodule:: _pytest.hookspec
+
 Bootstrapping hooks
 ~~~~~~~~~~~~~~~~~~~
 
-Bootstrapping hooks called for plugins registered early enough (internal and setuptools plugins).
+Bootstrapping hooks called for plugins registered early enough (internal and third-party plugins).
 
+.. hook:: pytest_load_initial_conftests
 .. autofunction:: pytest_load_initial_conftests
-.. autofunction:: pytest_cmdline_preparse
+.. hook:: pytest_cmdline_parse
 .. autofunction:: pytest_cmdline_parse
+.. hook:: pytest_cmdline_main
 .. autofunction:: pytest_cmdline_main
 
 .. _`initialization-hooks`:
@@ -683,13 +687,20 @@ Initialization hooks
 
 Initialization hooks called for plugins and ``conftest.py`` files.
 
+.. hook:: pytest_addoption
 .. autofunction:: pytest_addoption
+.. hook:: pytest_addhooks
 .. autofunction:: pytest_addhooks
+.. hook:: pytest_configure
 .. autofunction:: pytest_configure
+.. hook:: pytest_unconfigure
 .. autofunction:: pytest_unconfigure
+.. hook:: pytest_sessionstart
 .. autofunction:: pytest_sessionstart
+.. hook:: pytest_sessionfinish
 .. autofunction:: pytest_sessionfinish
 
+.. hook:: pytest_plugin_registered
 .. autofunction:: pytest_plugin_registered
 
 Collection hooks
@@ -697,21 +708,36 @@ Collection hooks
 
 ``pytest`` calls the following hooks for collecting files and directories:
 
+.. hook:: pytest_collection
 .. autofunction:: pytest_collection
+.. hook:: pytest_ignore_collect
 .. autofunction:: pytest_ignore_collect
+.. hook:: pytest_collect_directory
+.. autofunction:: pytest_collect_directory
+.. hook:: pytest_collect_file
 .. autofunction:: pytest_collect_file
+.. hook:: pytest_pycollect_makemodule
 .. autofunction:: pytest_pycollect_makemodule
 
 For influencing the collection of objects in Python modules
 you can use the following hook:
 
+.. hook:: pytest_pycollect_makeitem
 .. autofunction:: pytest_pycollect_makeitem
+.. hook:: pytest_generate_tests
 .. autofunction:: pytest_generate_tests
+.. hook:: pytest_make_parametrize_id
 .. autofunction:: pytest_make_parametrize_id
 
+Hooks for influencing test skipping:
+
+.. hook:: pytest_markeval_namespace
+.. autofunction:: pytest_markeval_namespace
+
 After collection is complete, you can modify the order of
 items, delete or otherwise amend the test items:
 
+.. hook:: pytest_collection_modifyitems
 .. autofunction:: pytest_collection_modifyitems
 
 .. note::
@@ -725,13 +751,21 @@ Test running (runtest) hooks
 
 All runtest related hooks receive a :py:class:`pytest.Item <pytest.Item>` object.
 
+.. hook:: pytest_runtestloop
 .. autofunction:: pytest_runtestloop
+.. hook:: pytest_runtest_protocol
 .. autofunction:: pytest_runtest_protocol
+.. hook:: pytest_runtest_logstart
 .. autofunction:: pytest_runtest_logstart
+.. hook:: pytest_runtest_logfinish
 .. autofunction:: pytest_runtest_logfinish
+.. hook:: pytest_runtest_setup
 .. autofunction:: pytest_runtest_setup
+.. hook:: pytest_runtest_call
 .. autofunction:: pytest_runtest_call
+.. hook:: pytest_runtest_teardown
 .. autofunction:: pytest_runtest_teardown
+.. hook:: pytest_runtest_makereport
 .. autofunction:: pytest_runtest_makereport
 
 For deeper understanding you may look at the default implementation of
@@ -740,6 +774,7 @@ in ``_pytest.pdb`` which interacts with ``_pytest.capture``
 and its input/output capturing in order to immediately drop
 into interactive debugging when a test failure occurs.
 
+.. hook:: pytest_pyfunc_call
 .. autofunction:: pytest_pyfunc_call
 
 Reporting hooks
@@ -747,27 +782,45 @@ Reporting hooks
 
 Session related reporting hooks:
 
+.. hook:: pytest_collectstart
 .. autofunction:: pytest_collectstart
+.. hook:: pytest_make_collect_report
 .. autofunction:: pytest_make_collect_report
+.. hook:: pytest_itemcollected
 .. autofunction:: pytest_itemcollected
+.. hook:: pytest_collectreport
 .. autofunction:: pytest_collectreport
+.. hook:: pytest_deselected
 .. autofunction:: pytest_deselected
+.. hook:: pytest_report_header
 .. autofunction:: pytest_report_header
+.. hook:: pytest_report_collectionfinish
 .. autofunction:: pytest_report_collectionfinish
+.. hook:: pytest_report_teststatus
 .. autofunction:: pytest_report_teststatus
+.. hook:: pytest_report_to_serializable
+.. autofunction:: pytest_report_to_serializable
+.. hook:: pytest_report_from_serializable
+.. autofunction:: pytest_report_from_serializable
+.. hook:: pytest_terminal_summary
 .. autofunction:: pytest_terminal_summary
+.. hook:: pytest_fixture_setup
 .. autofunction:: pytest_fixture_setup
+.. hook:: pytest_fixture_post_finalizer
 .. autofunction:: pytest_fixture_post_finalizer
-.. autofunction:: pytest_warning_captured
+.. hook:: pytest_warning_recorded
 .. autofunction:: pytest_warning_recorded
 
 Central hook for reporting about test execution:
 
+.. hook:: pytest_runtest_logreport
 .. autofunction:: pytest_runtest_logreport
 
 Assertion related hooks:
 
+.. hook:: pytest_assertrepr_compare
 .. autofunction:: pytest_assertrepr_compare
+.. hook:: pytest_assertion_pass
 .. autofunction:: pytest_assertion_pass
 
 
@@ -777,29 +830,28 @@ Debugging/Interaction hooks
 There are few hooks which can be used for special
 reporting or interaction with exceptions:
 
+.. hook:: pytest_internalerror
 .. autofunction:: pytest_internalerror
-.. autofunction:: pytest_keyboard_interrupt
-.. autofunction:: pytest_exception_interact
-.. autofunction:: pytest_enter_pdb
-
-
-Objects
--------
-
-Full reference to objects accessible from :ref:`fixtures <fixture>` or :ref:`hooks <hook-reference>`.
-
+.. hook:: pytest_keyboard_interrupt
+.. autofunction:: pytest_keyboard_interrupt
+.. hook:: pytest_exception_interact
+.. autofunction:: pytest_exception_interact
+.. hook:: pytest_enter_pdb
+.. autofunction:: pytest_enter_pdb
+.. hook:: pytest_leave_pdb
+.. autofunction:: pytest_leave_pdb
 
-CallInfo
-~~~~~~~~
 
-.. autoclass:: pytest.CallInfo()
-    :members:
+Collection tree objects
+-----------------------
 
+These are the collector and item classes (collectively called "nodes") which
+make up the collection tree.
 
-Class
-~~~~~
+Node
+~~~~
 
-.. autoclass:: pytest.Class()
+.. autoclass:: _pytest.nodes.Node()
     :members:
     :show-inheritance:
 
@@ -810,52 +862,52 @@ Collector
     :members:
     :show-inheritance:
 
-CollectReport
-~~~~~~~~~~~~~
+Item
+~~~~
 
-.. autoclass:: pytest.CollectReport()
+.. autoclass:: pytest.Item()
     :members:
     :show-inheritance:
-    :inherited-members:
 
-Config
-~~~~~~
+File
+~~~~
 
-.. autoclass:: pytest.Config()
+.. autoclass:: pytest.File()
     :members:
+    :show-inheritance:
 
-ExceptionInfo
-~~~~~~~~~~~~~
+FSCollector
+~~~~~~~~~~~
 
-.. autoclass:: pytest.ExceptionInfo()
+.. autoclass:: _pytest.nodes.FSCollector()
     :members:
+    :show-inheritance:
 
+Session
+~~~~~~~
 
-ExitCode
-~~~~~~~~
-
-.. autoclass:: pytest.ExitCode
+.. autoclass:: pytest.Session()
     :members:
+    :show-inheritance:
 
-File
-~~~~
+Package
+~~~~~~~
 
-.. autoclass:: pytest.File()
+.. autoclass:: pytest.Package()
     :members:
     :show-inheritance:
 
+Module
+~~~~~~
 
-FixtureDef
-~~~~~~~~~~
-
-.. autoclass:: _pytest.fixtures.FixtureDef()
+.. autoclass:: pytest.Module()
     :members:
     :show-inheritance:
 
-FSCollector
-~~~~~~~~~~~
+Class
+~~~~~
 
-.. autoclass:: _pytest.nodes.FSCollector()
+.. autoclass:: pytest.Class()
     :members:
     :show-inheritance:
 
@@ -873,10 +925,64 @@ FunctionDefinition
     :members:
     :show-inheritance:
 
-Item
-~~~~
 
-.. autoclass:: pytest.Item()
+Objects
+-------
+
+Objects accessible from :ref:`fixtures <fixture>` or :ref:`hooks <hook-reference>`
+or importable from ``pytest``.
+
+
+CallInfo
+~~~~~~~~
+
+.. autoclass:: pytest.CallInfo()
+    :members:
+
+CollectReport
+~~~~~~~~~~~~~
+
+.. autoclass:: pytest.CollectReport()
+    :members:
+    :show-inheritance:
+    :inherited-members:
+
+Config
+~~~~~~
+
+.. autoclass:: pytest.Config()
+    :members:
+
+Dir
+~~~
+
+.. autoclass:: pytest.Dir()
+    :members:
+
+Directory
+~~~~~~~~~
+
+.. autoclass:: pytest.Directory()
+    :members:
+
+ExceptionInfo
+~~~~~~~~~~~~~
+
+.. autoclass:: pytest.ExceptionInfo()
+    :members:
+
+
+ExitCode
+~~~~~~~~
+
+.. autoclass:: pytest.ExitCode
+    :members:
+
+
+FixtureDef
+~~~~~~~~~~
+
+.. autoclass:: pytest.FixtureDef()
     :members:
     :show-inheritance:
 
@@ -907,19 +1013,6 @@ Metafunc
 .. autoclass:: pytest.Metafunc()
     :members:
 
-Module
-~~~~~~
-
-.. autoclass:: pytest.Module()
-    :members:
-    :show-inheritance:
-
-Node
-~~~~
-
-.. autoclass:: _pytest.nodes.Node()
-    :members:
-
 Parser
 ~~~~~~
 
@@ -941,12 +1034,29 @@ PytestPluginManager
     :inherited-members:
     :show-inheritance:
 
-Session
-~~~~~~~
+RaisesExc
+~~~~~~~~~
 
-.. autoclass:: pytest.Session()
+.. autoclass:: pytest.RaisesExc()
     :members:
-    :show-inheritance:
+
+    .. autoattribute:: fail_reason
+
+RaisesGroup
+~~~~~~~~~~~
+**Tutorial**: :ref:`assert-matching-exception-groups`
+
+.. autoclass:: pytest.RaisesGroup()
+    :members:
+
+    .. autoattribute:: fail_reason
+
+TerminalReporter
+~~~~~~~~~~~~~~~~
+
+.. autoclass:: pytest.TerminalReporter
+    :members:
+    :inherited-members:
 
 TestReport
 ~~~~~~~~~~
@@ -956,10 +1066,16 @@ TestReport
     :show-inheritance:
     :inherited-members:
 
-_Result
+TestShortLogReport
+~~~~~~~~~~~~~~~~~~
+
+.. autoclass:: pytest.TestShortLogReport()
+    :members:
+
+Result
 ~~~~~~~
 
-Result object used within :ref:`hook wrappers <hookwrapper>`, see :py:class:`_Result in the pluggy documentation <pluggy._callers._Result>` for more information.
+Result object used within :ref:`hook wrappers <hookwrapper>`, see :py:class:`Result in the pluggy documentation <pluggy.Result>` for more information.
 
 Stash
 ~~~~~
@@ -1047,11 +1163,24 @@ Environment Variables
 
 Environment variables that can be used to change pytest's behavior.
 
+.. envvar:: CI
+
+When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to ``BUILD_NUMBER`` variable. See also :ref:`ci-pipelines`.
+
+.. envvar:: BUILD_NUMBER
+
+When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to CI variable. See also :ref:`ci-pipelines`.
+
 .. envvar:: PYTEST_ADDOPTS
 
 This contains a command-line (parsed by the py:mod:`shlex` module) that will be **prepended** to the command line given
 by the user, see :ref:`adding default options` for more information.
 
+.. envvar:: PYTEST_VERSION
+
+This environment variable is defined at the start of the pytest session and is undefined afterwards.
+It contains the value of ``pytest.__version__``, and among other things can be used to easily check if a code is running from within a pytest run.
+
 .. envvar:: PYTEST_CURRENT_TEST
 
 This is not meant to be set by users, but is set by pytest internally with the name of the current test so other
@@ -1061,10 +1190,17 @@ processes can inspect it, see :ref:`pytest current test env` for more informatio
 
 When set, pytest will print tracing and debug information.
 
+.. envvar:: PYTEST_DEBUG_TEMPROOT
+
+Root for temporary directories produced by fixtures like :fixture:`tmp_path`
+as discussed in :ref:`temporary directory location and retention`.
+
 .. envvar:: PYTEST_DISABLE_PLUGIN_AUTOLOAD
 
-When set, disables plugin auto-loading through setuptools entrypoints. Only explicitly specified plugins will be
-loaded.
+When set, disables plugin auto-loading through :std:doc:`entry point packaging
+metadata <packaging:guides/creating-and-discovering-plugins>`. Only plugins
+explicitly specified in :envvar:`PYTEST_PLUGINS` or with ``-p`` will be loaded.
+See also :ref:`--disable-plugin-autoload <disable_plugin_autoload>`.
 
 .. envvar:: PYTEST_PLUGINS
 
@@ -1074,6 +1210,8 @@ Contains comma-separated list of modules that should be loaded as plugins:
 
     export PYTEST_PLUGINS=mymodule.plugin,xdist
 
+See also ``-p``.
+
 .. envvar:: PYTEST_THEME
 
 Sets a `pygment style <https://pygments.org/docs/styles/>`_ to use for the code output.
@@ -1090,19 +1228,22 @@ When set to ``0``, pytest will not use color.
 
 .. envvar:: NO_COLOR
 
-When set (regardless of value), pytest will not use color in terminal output.
+When set to a non-empty string (regardless of value), pytest will not use color in terminal output.
 ``PY_COLORS`` takes precedence over ``NO_COLOR``, which takes precedence over ``FORCE_COLOR``.
 See `no-color.org <https://no-color.org/>`__ for other libraries supporting this community standard.
 
 .. envvar:: FORCE_COLOR
 
-When set (regardless of value), pytest will use color in terminal output.
+When set to a non-empty string (regardless of value), pytest will use color in terminal output.
 ``PY_COLORS`` and ``NO_COLOR`` take precedence over ``FORCE_COLOR``.
 
 Exceptions
 ----------
 
-.. autoclass:: pytest.UsageError()
+.. autoexception:: pytest.UsageError()
+    :show-inheritance:
+
+.. autoexception:: pytest.FixtureLookupError()
     :show-inheritance:
 
 .. _`warnings ref`:
@@ -1133,8 +1274,8 @@ Custom warnings generated in some situations such as improper usage or deprecate
 .. autoclass:: pytest.PytestExperimentalApiWarning
    :show-inheritance:
 
-.. autoclass:: pytest.PytestUnhandledCoroutineWarning
-   :show-inheritance:
+.. autoclass:: pytest.PytestRemovedIn9Warning
+  :show-inheritance:
 
 .. autoclass:: pytest.PytestUnknownMarkWarning
    :show-inheritance:
@@ -1154,9 +1295,10 @@ Consult the :ref:`internal-warnings` section in the documentation for more infor
 Configuration Options
 ---------------------
 
-Here is a list of builtin configuration options that may be written in a ``pytest.ini``, ``pyproject.toml``, ``tox.ini`` or ``setup.cfg``
-file, usually located at the root of your repository. To see each file format in details, see
-:ref:`config file formats`.
+Here is a list of builtin configuration options that may be written in a ``pytest.ini`` (or ``.pytest.ini``),
+``pyproject.toml``, ``tox.ini``, or ``setup.cfg`` file, usually located at the root of your repository.
+
+To see each file format in details, see :ref:`config file formats`.
 
 .. warning::
     Usage of ``setup.cfg`` is not recommended except for very simple use cases. ``.cfg``
@@ -1192,20 +1334,68 @@ passed multiple times. The expected format is ``name=value``. For example::
 
 .. confval:: cache_dir
 
-   Sets a directory where stores content of cache plugin. Default directory is
+   Sets the directory where the cache plugin's content is stored. Default directory is
    ``.pytest_cache`` which is created in :ref:`rootdir <rootdir>`. Directory may be
    relative or absolute path. If setting relative path, then directory is created
-   relative to :ref:`rootdir <rootdir>`. Additionally path may contain environment
+   relative to :ref:`rootdir <rootdir>`. Additionally, a path may contain environment
    variables, that will be expanded. For more information about cache plugin
    please refer to :ref:`cache_provider`.
 
+.. confval:: collect_imported_tests
+
+   .. versionadded:: 8.4
+
+   Setting this to ``false`` will make pytest collect classes/functions from test
+   files **only** if they are defined in that file (as opposed to imported there).
+
+   .. code-block:: ini
+
+        [pytest]
+        collect_imported_tests = false
+
+   Default: ``true``
+
+   pytest traditionally collects classes/functions in the test module namespace even if they are imported from another file.
+
+   For example:
+
+   .. code-block:: python
+
+       # contents of src/domain.py
+       class Testament: ...
+
+
+       # contents of tests/test_testament.py
+       from domain import Testament
+
+
+       def test_testament(): ...
+
+   In this scenario, with the default options, pytest will collect the class `Testament` from `tests/test_testament.py` because it starts with `Test`, even though in this case it is a production class being imported in the test module namespace.
+
+   Set ``collected_imported_tests`` to ``false`` in the configuration file prevents that.
+
+.. confval:: consider_namespace_packages
+
+   Controls if pytest should attempt to identify `namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages>`__
+   when collecting Python modules. Default is ``False``.
+
+   Set to ``True`` if the package you are testing is part of a namespace package.
+
+   Only `native namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages/#native-namespace-packages>`__
+   are supported, with no plans to support `legacy namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages/#legacy-namespace-packages>`__.
+
+   .. versionadded:: 8.1
+
 .. confval:: console_output_style
 
    Sets the console output style while running tests:
 
    * ``classic``: classic pytest output.
    * ``progress``: like classic pytest output, but with a progress indicator.
+   * ``progress-even-when-capture-no``: allows the use of the progress indicator even when ``capture=no``.
    * ``count``: like progress, but shows progress as the number of tests completed instead of a percent.
+   * ``times``: show tests duration.
 
    The default is ``progress``, but you can fallback to ``classic`` if you prefer or
    the new mode is causing unexpected problems:
@@ -1217,6 +1407,29 @@ passed multiple times. The expected format is ``name=value``. For example::
         console_output_style = classic
 
 
+.. confval:: disable_test_id_escaping_and_forfeit_all_rights_to_community_support
+
+   .. versionadded:: 4.4
+
+   pytest by default escapes any non-ascii characters used in unicode strings
+   for the parametrization because it has several downsides.
+   If however you would like to use unicode strings in parametrization
+   and see them in the terminal as is (non-escaped), use this option
+   in your ``pytest.ini``:
+
+   .. code-block:: ini
+
+       [pytest]
+       disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
+
+   Keep in mind however that this might cause unwanted side effects and
+   even bugs depending on the OS used and plugins currently installed,
+   so use it at your own risk.
+
+   Default: ``False``.
+
+   See :ref:`parametrizemark`.
+
 .. confval:: doctest_encoding
 
 
@@ -1455,7 +1668,7 @@ passed multiple times. The expected format is ``name=value``. For example::
 
 
 
-    Sets a file name relative to the ``pytest.ini`` file where log messages should be written to, in addition
+    Sets a file name relative to the current working directory where log messages should be written to, in addition
     to the other logging facilities that are active.
 
     .. code-block:: ini
@@ -1594,14 +1807,14 @@ passed multiple times. The expected format is ``name=value``. For example::
    This would tell ``pytest`` to not look into typical subversion or
    sphinx-build directories or into any ``tmp`` prefixed directory.
 
-   Additionally, ``pytest`` will attempt to intelligently identify and ignore a
-   virtualenv by the presence of an activation script.  Any directory deemed to
-   be the root of a virtual environment will not be considered during test
-   collection unless ``‑‑collect‑in‑virtualenv`` is given.  Note also that
-   ``norecursedirs`` takes precedence over ``‑‑collect‑in‑virtualenv``; e.g. if
-   you intend to run tests in a virtualenv with a base directory that matches
-   ``'.*'`` you *must* override ``norecursedirs`` in addition to using the
-   ``‑‑collect‑in‑virtualenv`` flag.
+   Additionally, ``pytest`` will attempt to intelligently identify and ignore
+   a virtualenv.  Any directory deemed to be the root of a virtual environment
+   will not be considered during test collection unless
+   ``--collect-in-virtualenv`` is given.  Note also that ``norecursedirs``
+   takes precedence over ``--collect-in-virtualenv``; e.g. if you intend to
+   run tests in a virtualenv with a base directory that matches ``'.*'`` you
+   *must* override ``norecursedirs`` in addition to using the
+   ``--collect-in-virtualenv`` flag.
 
 
 .. confval:: python_classes
@@ -1697,11 +1910,12 @@ passed multiple times. The expected format is ``name=value``. For example::
 
 .. confval:: testpaths
 
-
-
    Sets list of directories that should be searched for tests when
    no specific directories, files or test ids are given in the command line when
    executing pytest from the :ref:`rootdir <rootdir>` directory.
+   File system paths may use shell-style wildcards, including the recursive
+   ``**`` pattern.
+
    Useful when all project tests are in a known location to speed up
    test collection and to avoid picking up undesired tests by accident.
 
@@ -1710,8 +1924,88 @@ passed multiple times. The expected format is ``name=value``. For example::
         [pytest]
         testpaths = testing doc
 
-   This tells pytest to only look for tests in ``testing`` and ``doc``
-   directories when executing from the root directory.
+   This configuration means that executing:
+
+   .. code-block:: console
+
+       pytest
+
+   has the same practical effects as executing:
+
+   .. code-block:: console
+
+       pytest testing doc
+
+.. confval:: tmp_path_retention_count
+
+   How many sessions should we keep the `tmp_path` directories,
+   according to `tmp_path_retention_policy`.
+
+   .. code-block:: ini
+
+        [pytest]
+        tmp_path_retention_count = 3
+
+   Default: ``3``
+
+
+.. confval:: tmp_path_retention_policy
+
+
+
+   Controls which directories created by the `tmp_path` fixture are kept around,
+   based on test outcome.
+
+    * `all`: retains directories for all tests, regardless of the outcome.
+    * `failed`: retains directories only for tests with outcome `error` or `failed`.
+    * `none`: directories are always removed after each test ends, regardless of the outcome.
+
+   .. code-block:: ini
+
+        [pytest]
+        tmp_path_retention_policy = all
+
+   Default: ``all``
+
+
+.. confval:: truncation_limit_chars
+
+   Controls maximum number of characters to truncate assertion message contents.
+
+   Setting value to ``0`` disables the character limit for truncation.
+
+   .. code-block:: ini
+
+       [pytest]
+       truncation_limit_chars = 640
+
+   pytest truncates the assert messages to a certain limit by default to prevent comparison with large data to overload the console output.
+
+   Default: ``640``
+
+   .. note::
+
+        If pytest detects it is :ref:`running on CI <ci-pipelines>`, truncation is disabled automatically.
+
+
+.. confval:: truncation_limit_lines
+
+   Controls maximum number of linesto truncate assertion message contents.
+
+   Setting value to ``0`` disables the lines limit for truncation.
+
+   .. code-block:: ini
+
+       [pytest]
+       truncation_limit_lines = 8
+
+   pytest truncates the assert messages to a certain limit by default to prevent comparison with large data to overload the console output.
+
+   Default: ``8``
+
+   .. note::
+
+        If pytest detects it is :ref:`running on CI <ci-pipelines>`, truncation is disabled automatically.
 
 
 .. confval:: usefixtures
@@ -1727,6 +2021,32 @@ passed multiple times. The expected format is ``name=value``. For example::
             clean_db
 
 
+.. confval:: verbosity_assertions
+
+    Set a verbosity level specifically for assertion related output, overriding the application wide level.
+
+    .. code-block:: ini
+
+        [pytest]
+        verbosity_assertions = 2
+
+    Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
+    "auto" can be used to explicitly use the global verbosity level.
+
+
+.. confval:: verbosity_test_cases
+
+    Set a verbosity level specifically for test case execution related output, overriding the application wide level.
+
+    .. code-block:: ini
+
+        [pytest]
+        verbosity_test_cases = 2
+
+    Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
+    "auto" can be used to explicitly use the global verbosity level.
+
+
 .. confval:: xfail_strict
 
     If set to ``True``, tests marked with ``@pytest.mark.xfail`` that actually succeed will by default fail the
@@ -1754,8 +2074,8 @@ All the command-line flags can be obtained by running ``pytest --help``::
       file_or_dir
 
     general:
-      -k EXPRESSION         only run tests which match the given substring
-                            expression. An expression is a python evaluatable
+      -k EXPRESSION         Only run tests which match the given substring
+                            expression. An expression is a Python evaluable
                             expression where all names are substring-matched
                             against test names and their parent classes.
                             Example: -k 'test_method or test_other' matches all
@@ -1769,93 +2089,102 @@ All the command-line flags can be obtained by running ``pytest --help``::
                             'extra_keyword_matches' set, as well as functions
                             which have names assigned directly to them. The
                             matching is case-insensitive.
-      -m MARKEXPR           only run tests matching given mark expression.
-                            For example: -m 'mark1 and not mark2'.
+      -m MARKEXPR           Only run tests matching given mark expression. For
+                            example: -m 'mark1 and not mark2'.
       --markers             show markers (builtin, plugin and per-project ones).
-      -x, --exitfirst       exit instantly on first error or failed test.
+      -x, --exitfirst       Exit instantly on first error or failed test
       --fixtures, --funcargs
-                            show available fixtures, sorted by plugin appearance
+                            Show available fixtures, sorted by plugin appearance
                             (fixtures with leading '_' are only shown with '-v')
-      --fixtures-per-test   show fixtures per test
-      --pdb                 start the interactive Python debugger on errors or
-                            KeyboardInterrupt.
+      --fixtures-per-test   Show fixtures per test
+      --pdb                 Start the interactive Python debugger on errors or
+                            KeyboardInterrupt
       --pdbcls=modulename:classname
-                            specify a custom interactive Python debugger for use
+                            Specify a custom interactive Python debugger for use
                             with --pdb.For example:
                             --pdbcls=IPython.terminal.debugger:TerminalPdb
-      --trace               Immediately break when running each test.
-      --capture=method      per-test capturing method: one of fd|sys|no|tee-sys.
-      -s                    shortcut for --capture=no.
-      --runxfail            report the results of xfail tests as if they were
+      --trace               Immediately break when running each test
+      --capture=method      Per-test capturing method: one of fd|sys|no|tee-sys
+      -s                    Shortcut for --capture=no
+      --runxfail            Report the results of xfail tests as if they were
                             not marked
-      --lf, --last-failed   rerun only the tests that failed at the last run (or
+      --lf, --last-failed   Rerun only the tests that failed at the last run (or
                             all if none failed)
-      --ff, --failed-first  run all tests, but run the last failures first.
-                            This may re-order tests and thus lead to repeated
-                            fixture setup/teardown.
-      --nf, --new-first     run tests from new files first, then the rest of the
+      --ff, --failed-first  Run all tests, but run the last failures first. This
+                            may re-order tests and thus lead to repeated fixture
+                            setup/teardown.
+      --nf, --new-first     Run tests from new files first, then the rest of the
                             tests sorted by file mtime
       --cache-show=[CACHESHOW]
-                            show cache contents, don't perform collection or
+                            Show cache contents, don't perform collection or
                             tests. Optional argument: glob (default: '*').
-      --cache-clear         remove all cache contents at start of test run.
-      --lfnf={all,none}, --last-failed-no-failures={all,none}
-                            which tests to run with no previously (known)
-                            failures.
-      --sw, --stepwise      exit on test failure and continue from last failing
+      --cache-clear         Remove all cache contents at start of test run
+      --lfnf, --last-failed-no-failures={all,none}
+                            With ``--lf``, determines whether to execute tests
+                            when there are no previously (known) failures or
+                            when no cached ``lastfailed`` data was found.
+                            ``all`` (the default) runs the full test suite
+                            again. ``none`` just emits a message about no known
+                            failures and exits successfully.
+      --sw, --stepwise      Exit on test failure and continue from last failing
                             test next time
       --sw-skip, --stepwise-skip
-                            ignore the first failing test but stop on the next
-                            failing test.
-                            implicitly enables --stepwise.
+                            Ignore the first failing test but stop on the next
+                            failing test. Implicitly enables --stepwise.
 
-    reporting:
-      --durations=N         show N slowest setup/test durations (N=0 for all).
+    Reporting:
+      --durations=N         Show N slowest setup/test durations (N=0 for all)
       --durations-min=N     Minimal duration in seconds for inclusion in slowest
-                            list. Default 0.005
-      -v, --verbose         increase verbosity.
-      --no-header           disable header
-      --no-summary          disable summary
-      -q, --quiet           decrease verbosity.
-      --verbosity=VERBOSE   set verbosity. Default is 0.
-      -r chars              show extra test summary info as specified by chars:
+                            list. Default: 0.005.
+      -v, --verbose         Increase verbosity
+      --no-header           Disable header
+      --no-summary          Disable summary
+      --no-fold-skipped     Do not fold skipped tests in short summary.
+      -q, --quiet           Decrease verbosity
+      --verbosity=VERBOSE   Set verbosity. Default: 0.
+      -r chars              Show extra test summary info as specified by chars:
                             (f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed,
                             (p)assed, (P)assed with output, (a)ll except passed
                             (p/P), or (A)ll. (w)arnings are enabled by default
                             (see --disable-warnings), 'N' can be used to reset
                             the list. (default: 'fE').
       --disable-warnings, --disable-pytest-warnings
-                            disable warnings summary
-      -l, --showlocals      show locals in tracebacks (disabled by default).
-      --tb=style            traceback print mode
-                            (auto/long/short/line/native/no).
+                            Disable warnings summary
+      -l, --showlocals      Show locals in tracebacks (disabled by default)
+      --no-showlocals       Hide locals in tracebacks (negate --showlocals
+                            passed through addopts)
+      --tb=style            Traceback print mode
+                            (auto/long/short/line/native/no)
+      --xfail-tb            Show tracebacks for xfail (as long as --tb != no)
       --show-capture={no,stdout,stderr,log,all}
                             Controls how captured stdout/stderr/log is shown on
-                            failed tests. Default is 'all'.
-      --full-trace          don't cut any tracebacks (default is to cut).
-      --color=color         color terminal output (yes/no/auto).
+                            failed tests. Default: all.
+      --full-trace          Don't cut any tracebacks (default is to cut)
+      --color=color         Color terminal output (yes/no/auto)
       --code-highlight={yes,no}
                             Whether code should be highlighted (only if --color
-                            is also enabled)
-      --pastebin=mode       send failed|all info to bpaste.net pastebin service.
-      --junit-xml=path      create junit-xml style report file at given path.
-      --junit-prefix=str    prepend prefix to classnames in junit-xml output
+                            is also enabled). Default: yes.
+      --pastebin=mode       Send failed|all info to bpaste.net pastebin service
+      --junitxml, --junit-xml=path
+                            Create junit-xml style report file at given path
+      --junitprefix, --junit-prefix=str
+                            Prepend prefix to classnames in junit-xml output
 
     pytest-warnings:
-      -W PYTHONWARNINGS, --pythonwarnings=PYTHONWARNINGS
-                            set which warnings to report, see -W option of
-                            python itself.
-      --maxfail=num         exit after first num failures or errors.
-      --strict-config       any warnings encountered while parsing the `pytest`
-                            section of the configuration file raise errors.
-      --strict-markers      markers not registered in the `markers` section of
-                            the configuration file raise errors.
-      --strict              (deprecated) alias to --strict-markers.
-      -c file               load configuration from `file` instead of trying to
+      -W, --pythonwarnings PYTHONWARNINGS
+                            Set which warnings to report, see -W option of
+                            Python itself
+      --maxfail=num         Exit after first num failures or errors
+      --strict-config       Any warnings encountered while parsing the `pytest`
+                            section of the configuration file raise errors
+      --strict-markers      Markers not registered in the `markers` section of
+                            the configuration file raise errors
+      --strict              (Deprecated) alias to --strict-markers
+      -c, --config-file FILE
+                            Load configuration from `FILE` instead of trying to
                             locate one of the implicit configuration files.
       --continue-on-collection-errors
-                            Force test execution even if collection errors
-                            occur.
+                            Force test execution even if collection errors occur
       --rootdir=ROOTDIR     Define root directory for tests. Can be relative
                             path: 'root_dir', './root_dir',
                             'root_dir/another_dir/'; absolute path:
@@ -1863,124 +2192,147 @@ All the command-line flags can be obtained by running ``pytest --help``::
                             '$HOME/root_dir'.
 
     collection:
-      --collect-only, --co  only collect tests, don't execute them.
-      --pyargs              try to interpret all arguments as python packages.
-      --ignore=path         ignore path during collection (multi-allowed).
-      --ignore-glob=path    ignore path pattern during collection (multi-
-                            allowed).
+      --collect-only, --co  Only collect tests, don't execute them
+      --pyargs              Try to interpret all arguments as Python packages
+      --ignore=path         Ignore path during collection (multi-allowed)
+      --ignore-glob=path    Ignore path pattern during collection (multi-
+                            allowed)
       --deselect=nodeid_prefix
-                            deselect item (via node id prefix) during collection
-                            (multi-allowed).
-      --confcutdir=dir      only load conftest.py's relative to specified dir.
-      --noconftest          Don't load any conftest.py files.
-      --keep-duplicates     Keep duplicate tests.
+                            Deselect item (via node id prefix) during collection
+                            (multi-allowed)
+      --confcutdir=dir      Only load conftest.py's relative to specified dir
+      --noconftest          Don't load any conftest.py files
+      --keep-duplicates     Keep duplicate tests
       --collect-in-virtualenv
                             Don't ignore tests in a local virtualenv directory
       --import-mode={prepend,append,importlib}
-                            prepend/append to sys.path when importing test
-                            modules and conftest files, default is to prepend.
-      --doctest-modules     run doctests in all .py modules
+                            Prepend/append to sys.path when importing test
+                            modules and conftest files. Default: prepend.
+      --doctest-modules     Run doctests in all .py modules
       --doctest-report={none,cdiff,ndiff,udiff,only_first_failure}
-                            choose another output format for diffs on doctest
+                            Choose another output format for diffs on doctest
                             failure
-      --doctest-glob=pat    doctests file matching pattern, default: test*.txt
+      --doctest-glob=pat    Doctests file matching pattern, default: test*.txt
       --doctest-ignore-import-errors
-                            ignore doctest ImportErrors
+                            Ignore doctest collection errors
       --doctest-continue-on-failure
-                            for a given doctest, continue to run after the first
+                            For a given doctest, continue to run after the first
                             failure
 
     test session debugging and configuration:
-      --basetemp=dir        base temporary directory for this test run.(warning:
-                            this directory is removed if it exists)
-      -V, --version         display pytest version and information about
+      --basetemp=dir        Base temporary directory for this test run.
+                            (Warning: this directory is removed if it exists.)
+      -V, --version         Display pytest version and information about
                             plugins. When given twice, also display information
                             about plugins.
-      -h, --help            show help message and configuration info
-      -p name               early-load given plugin module name or entry point
-                            (multi-allowed).
-                            To avoid loading of plugins, use the `no:` prefix,
-                            e.g. `no:doctest`.
-      --trace-config        trace considerations of conftest.py files.
+      -h, --help            Show help message and configuration info
+      -p name               Early-load given plugin module name or entry point
+                            (multi-allowed). To avoid loading of plugins, use
+                            the `no:` prefix, e.g. `no:doctest`.
+      --trace-config        Trace considerations of conftest.py files
       --debug=[DEBUG_FILE_NAME]
-                            store internal tracing debug information in this log
-                            file.
-                            This file is opened with 'w' and truncated as a
-                            result, care advised.
-                            Defaults to 'pytestdebug.log'.
-      -o OVERRIDE_INI, --override-ini=OVERRIDE_INI
-                            override ini option with "option=value" style, e.g.
+                            Store internal tracing debug information in this log
+                            file. This file is opened with 'w' and truncated as
+                            a result, care advised. Default: pytestdebug.log.
+      -o, --override-ini OVERRIDE_INI
+                            Override ini option with "option=value" style, e.g.
                             `-o xfail_strict=True -o cache_dir=cache`.
       --assert=MODE         Control assertion debugging tools.
                             'plain' performs no assertion debugging.
                             'rewrite' (the default) rewrites assert statements
                             in test modules on import to provide assert
                             expression information.
-      --setup-only          only setup fixtures, do not execute tests.
-      --setup-show          show setup of fixtures while executing tests.
-      --setup-plan          show what fixtures and tests would be executed but
-                            don't execute anything.
+      --setup-only          Only setup fixtures, do not execute tests
+      --setup-show          Show setup of fixtures while executing tests
+      --setup-plan          Show what fixtures and tests would be executed but
+                            don't execute anything
 
     logging:
-      --log-level=LEVEL     level of messages to catch/display.
-                            Not set by default, so it depends on the root/parent
-                            log handler's effective level, where it is "WARNING"
-                            by default.
+      --log-level=LEVEL     Level of messages to catch/display. Not set by
+                            default, so it depends on the root/parent log
+                            handler's effective level, where it is "WARNING" by
+                            default.
       --log-format=LOG_FORMAT
-                            log format as used by the logging module.
+                            Log format used by the logging module
       --log-date-format=LOG_DATE_FORMAT
-                            log date format as used by the logging module.
+                            Log date format used by the logging module
       --log-cli-level=LOG_CLI_LEVEL
-                            cli logging level.
+                            CLI logging level
       --log-cli-format=LOG_CLI_FORMAT
-                            log format as used by the logging module.
+                            Log format used by the logging module
       --log-cli-date-format=LOG_CLI_DATE_FORMAT
-                            log date format as used by the logging module.
-      --log-file=LOG_FILE   path to a file when logging will be written to.
+                            Log date format used by the logging module
+      --log-file=LOG_FILE   Path to a file when logging will be written to
+      --log-file-mode={w,a}
+                            Log file open mode
       --log-file-level=LOG_FILE_LEVEL
-                            log file logging level.
+                            Log file logging level
       --log-file-format=LOG_FILE_FORMAT
-                            log format as used by the logging module.
+                            Log format used by the logging module
       --log-file-date-format=LOG_FILE_DATE_FORMAT
-                            log date format as used by the logging module.
+                            Log date format used by the logging module
       --log-auto-indent=LOG_AUTO_INDENT
                             Auto-indent multiline messages passed to the logging
                             module. Accepts true|on, false|off or an integer.
+      --log-disable=LOGGER_DISABLE
+                            Disable a logger by name. Can be passed multiple
+                            times.
 
-    [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:
+    [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:
 
-      markers (linelist):   markers for test functions
+      markers (linelist):   Register new markers for test functions
       empty_parameter_set_mark (string):
-                            default marker for empty parametersets
-      norecursedirs (args): directory patterns to avoid for recursion
-      testpaths (args):     directories to search for tests when no files or
-                            directories are given in the command line.
+                            Default marker for empty parametersets
+      norecursedirs (args): Directory patterns to avoid for recursion
+      testpaths (args):     Directories to search for tests when no files or
+                            directories are given on the command line
       filterwarnings (linelist):
                             Each line specifies a pattern for
                             warnings.filterwarnings. Processed after
                             -W/--pythonwarnings.
-      usefixtures (args):   list of default fixtures to be used with this
+      consider_namespace_packages (bool):
+                            Consider namespace packages when resolving module
+                            names during import
+      usefixtures (args):   List of default fixtures to be used with this
                             project
-      python_files (args):  glob-style file patterns for Python test module
+      python_files (args):  Glob-style file patterns for Python test module
                             discovery
       python_classes (args):
-                            prefixes or glob names for Python test class
+                            Prefixes or glob names for Python test class
                             discovery
       python_functions (args):
-                            prefixes or glob names for Python test function and
+                            Prefixes or glob names for Python test function and
                             method discovery
       disable_test_id_escaping_and_forfeit_all_rights_to_community_support (bool):
-                            disable string escape non-ascii characters, might
+                            Disable string escape non-ASCII characters, might
                             cause unwanted side effects(use at your own risk)
       console_output_style (string):
-                            console output: "classic", or with additional
+                            Console output: "classic", or with additional
                             progress information ("progress" (percentage) |
-                            "count").
-      xfail_strict (bool):  default for the strict parameter of xfail markers
+                            "count" | "progress-even-when-capture-no" (forces
+                            progress even when capture=no)
+      verbosity_test_cases (string):
+                            Specify a verbosity level for test case execution,
+                            overriding the main level. Higher levels will
+                            provide more detailed information about each test
+                            case executed.
+      xfail_strict (bool):  Default for the strict parameter of xfail markers
                             when not given explicitly (default: False)
+      tmp_path_retention_count (string):
+                            How many sessions should we keep the `tmp_path`
+                            directories, according to
+                            `tmp_path_retention_policy`.
+      tmp_path_retention_policy (string):
+                            Controls which directories created by the `tmp_path`
+                            fixture are kept around, based on test outcome.
+                            (all/failed/none)
       enable_assertion_pass_hook (bool):
-                            Enables the pytest_assertion_pass hook.Make sure to
+                            Enables the pytest_assertion_pass hook. Make sure to
                             delete any previously generated pyc cache files.
+      verbosity_assertions (string):
+                            Specify a verbosity level for assertions, overriding
+                            the main level. Higher levels will provide more
+                            detailed explanation when an assertion fails.
       junit_suite_name (string):
                             Test suite name for JUnit report
       junit_logging (string):
@@ -1994,44 +2346,49 @@ All the command-line flags can be obtained by running ``pytest --help``::
       junit_family (string):
                             Emit XML for schema: one of legacy|xunit1|xunit2
       doctest_optionflags (args):
-                            option flags for doctests
+                            Option flags for doctests
       doctest_encoding (string):
-                            encoding used for doctest files
-      cache_dir (string):   cache directory path.
-      log_level (string):   default value for --log-level
-      log_format (string):  default value for --log-format
+                            Encoding used for doctest files
+      cache_dir (string):   Cache directory path
+      log_level (string):   Default value for --log-level
+      log_format (string):  Default value for --log-format
       log_date_format (string):
-                            default value for --log-date-format
-      log_cli (bool):       enable log display during test run (also known as
-                            "live logging").
+                            Default value for --log-date-format
+      log_cli (bool):       Enable log display during test run (also known as
+                            "live logging")
       log_cli_level (string):
-                            default value for --log-cli-level
+                            Default value for --log-cli-level
       log_cli_format (string):
-                            default value for --log-cli-format
+                            Default value for --log-cli-format
       log_cli_date_format (string):
-                            default value for --log-cli-date-format
-      log_file (string):    default value for --log-file
+                            Default value for --log-cli-date-format
+      log_file (string):    Default value for --log-file
+      log_file_mode (string):
+                            Default value for --log-file-mode
       log_file_level (string):
-                            default value for --log-file-level
+                            Default value for --log-file-level
       log_file_format (string):
-                            default value for --log-file-format
+                            Default value for --log-file-format
       log_file_date_format (string):
-                            default value for --log-file-date-format
+                            Default value for --log-file-date-format
       log_auto_indent (string):
-                            default value for --log-auto-indent
+                            Default value for --log-auto-indent
+      pythonpath (paths):   Add paths to sys.path
       faulthandler_timeout (string):
                             Dump the traceback of all threads if a test takes
-                            more than TIMEOUT seconds to finish.
-      addopts (args):       extra command line options
-      minversion (string):  minimally required pytest version
+                            more than TIMEOUT seconds to finish
+      addopts (args):       Extra command line options
+      minversion (string):  Minimally required pytest version
       required_plugins (args):
-                            plugins that must be present for pytest to run
-
-    environment variables:
-      PYTEST_ADDOPTS           extra command line options
-      PYTEST_PLUGINS           comma-separated plugins to load during startup
-      PYTEST_DISABLE_PLUGIN_AUTOLOAD set to disable plugin auto-loading
-      PYTEST_DEBUG             set to enable debug tracing of pytest's internals
+                            Plugins that must be present for pytest to run
+
+    Environment variables:
+      CI                       When set (regardless of value), pytest knows it is running in a CI process and does not truncate summary info
+      BUILD_NUMBER             Equivalent to CI
+      PYTEST_ADDOPTS           Extra command line options
+      PYTEST_PLUGINS           Comma-separated plugins to load during startup
+      PYTEST_DISABLE_PLUGIN_AUTOLOAD Set to disable plugin auto-loading
+      PYTEST_DEBUG             Set to enable debug tracing of pytest's internals
 
 
     to see available markers type: pytest --markers
diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt
index 5b49cb7fccc..ddcb7efb99b 100644
--- a/doc/en/requirements.txt
+++ b/doc/en/requirements.txt
@@ -1,7 +1,10 @@
-pallets-sphinx-themes
-pluggy>=1.0
-pygments-pytest>=2.2.0
+-c broken-dep-constraints.txt
+pluggy>=1.5.0
+pygments-pytest>=2.3.0
 sphinx-removed-in>=0.2.0
-sphinx>=3.1,<4
+sphinx>=7
 sphinxcontrib-trio
 sphinxcontrib-svg2pdfconverter
+furo
+sphinxcontrib-towncrier
+sphinx-issues
diff --git a/doc/en/talks.rst b/doc/en/talks.rst
index 6843c82bab5..b9b153a792e 100644
--- a/doc/en/talks.rst
+++ b/doc/en/talks.rst
@@ -11,9 +11,16 @@ Books
 - `Python Testing with pytest, by Brian Okken (2017)
   <https://pragprog.com/book/bopytest/python-testing-with-pytest>`_.
 
+- `Python Testing with pytest, Second Edition, by Brian Okken (2022)
+  <https://pragprog.com/titles/bopytest2/python-testing-with-pytest-second-edition>`_.
+
 Talks and blog postings
 ---------------------------------------------
 
+- Training: `pytest - simple, rapid and fun testing with Python <https://www.youtube.com/watch?v=ofPHJrAOaTE>`_, Florian Bruhin, PyConDE 2022
+
+- `pytest: Simple, rapid and fun testing with Python, <https://youtu.be/cSJ-X3TbQ1c?t=15752>`_ (@ 4:22:32), Florian Bruhin, WeAreDevelopers World Congress 2021
+
 - Webinar: `pytest: Test Driven Development für Python (German) <https://bruhin.software/ins-pytest/>`_, Florian Bruhin, via mylearning.ch, 2020
 
 - Webinar: `Simplify Your Tests with Fixtures <https://blog.jetbrains.com/pycharm/2020/08/webinar-recording-simplify-your-tests-with-fixtures-with-oliver-bestwalter/>`_, Oliver Bestwalter, via JetBrains, 2020
diff --git a/extra/get_issues.py b/extra/get_issues.py
index 4aaa3c3ec31..851d2f6d7f3 100644
--- a/extra/get_issues.py
+++ b/extra/get_issues.py
@@ -1,8 +1,12 @@
+from __future__ import annotations
+
 import json
 from pathlib import Path
+import sys
 
 import requests
 
+
 issues_url = "https://api.github.com/repos/pytest-dev/pytest/issues"
 
 
@@ -16,7 +20,7 @@ def get_issues():
         if r.status_code == 403:
             # API request limit exceeded
             print(data["message"])
-            exit(1)
+            sys.exit(1)
         issues.extend(data)
 
         # Look for next page
@@ -59,7 +63,7 @@ def report(issues):
         kind = _get_kind(issue)
         status = issue["state"]
         number = issue["number"]
-        link = "https://github.com/pytest-dev/pytest/issues/%s/" % number
+        link = f"https://github.com/pytest-dev/pytest/issues/{number}/"
         print("----")
         print(status, kind, link)
         print(title)
@@ -68,7 +72,7 @@ def report(issues):
         # print("\n".join(lines[:3]))
         # if len(lines) > 3 or len(body) > 240:
         #    print("...")
-    print("\n\nFound %s open issues" % len(issues))
+    print(f"\n\nFound {len(issues)} open issues")
 
 
 if __name__ == "__main__":
diff --git a/extra/setup-py.test/setup.py b/extra/setup-py.test/setup.py
deleted file mode 100644
index d0560ce1f5f..00000000000
--- a/extra/setup-py.test/setup.py
+++ /dev/null
@@ -1,11 +0,0 @@
-import sys
-from distutils.core import setup
-
-if __name__ == "__main__":
-    if "sdist" not in sys.argv[1:]:
-        raise ValueError("please use 'pytest' pypi package instead of 'py.test'")
-    setup(
-        name="py.test",
-        version="0.0",
-        description="please use 'pytest' for installation",
-    )
diff --git a/pyproject.toml b/pyproject.toml
index 5d32b755c74..c7db5947cf4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,24 +1,381 @@
 [build-system]
+build-backend = "setuptools.build_meta"
 requires = [
-  # sync with setup.py until we discard non-pep-517/518
-  "setuptools>=45.0",
-  "setuptools-scm[toml]>=6.2.3",
-  "wheel",
+    "setuptools>=61",
+    "setuptools-scm[toml]>=6.2.3",
+]
+
+[project]
+name = "pytest"
+description = "pytest: simple powerful testing with Python"
+readme = "README.rst"
+keywords = [
+    "test",
+    "unittest",
+]
+license = { text = "MIT" }
+authors = [
+    { name = "Holger Krekel" },
+    { name = "Bruno Oliveira" },
+    { name = "Ronny Pfannschmidt" },
+    { name = "Floris Bruynooghe" },
+    { name = "Brianna Laugher" },
+    { name = "Florian Bruhin" },
+    { name = "Others (See AUTHORS)" },
+]
+requires-python = ">=3.9"
+classifiers = [
+    "Development Status :: 6 - Mature",
+    "Intended Audience :: Developers",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: MacOS",
+    "Operating System :: Microsoft :: Windows",
+    "Operating System :: POSIX",
+    "Operating System :: Unix",
+    "Programming Language :: Python :: 3 :: Only",
+    "Programming Language :: Python :: 3.9",
+    "Programming Language :: Python :: 3.10",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
+    "Topic :: Software Development :: Libraries",
+    "Topic :: Software Development :: Testing",
+    "Topic :: Utilities",
+]
+dynamic = [
+    "version",
+]
+dependencies = [
+    "colorama>=0.4; sys_platform=='win32'",
+    "exceptiongroup>=1; python_version<'3.11'",
+    "iniconfig>=1",
+    "packaging>=20",
+    "pluggy>=1.5,<2",
+    "pygments>=2.7.2",
+    "tomli>=1; python_version<'3.11'",
+]
+optional-dependencies.dev = [
+    "argcomplete",
+    "attrs>=19.2",
+    "hypothesis>=3.56",
+    "mock",
+    "requests",
+    "setuptools",
+    "xmlschema",
+]
+urls.Changelog = "https://docs.pytest.org/en/stable/changelog.html"
+urls.Contact = "https://docs.pytest.org/en/stable/contact.html"
+urls.Funding = "https://docs.pytest.org/en/stable/sponsor.html"
+urls.Homepage = "https://docs.pytest.org/en/latest/"
+urls.Source = "https://github.com/pytest-dev/pytest"
+urls.Tracker = "https://github.com/pytest-dev/pytest/issues"
+scripts."py.test" = "pytest:console_main"
+scripts.pytest = "pytest:console_main"
+
+[tool.setuptools.package-data]
+"_pytest" = [
+    "py.typed",
+]
+"pytest" = [
+    "py.typed",
 ]
-build-backend = "setuptools.build_meta"
 
 [tool.setuptools_scm]
 write_to = "src/_pytest/_version.py"
 
+[tool.black]
+# See https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#t-target-version
+target-version = [ "py39", "py310", "py311", "py312", "py313" ]
+
+[tool.ruff]
+target-version = "py39"
+line-length = 88
+src = [
+    "src",
+]
+format.docstring-code-format = true
+lint.select = [
+    "B",       # bugbear
+    "D",       # pydocstyle
+    "E",       # pycodestyle
+    "F",       # pyflakes
+    "FA100",   # add future annotations
+    "I",       # isort
+    "PGH004",  # pygrep-hooks - Use specific rule codes when using noqa
+    "PIE",     # flake8-pie
+    "PLC",     # pylint convention
+    "PLE",     # pylint error
+    "PLR",     # pylint refactor
+    "PLR1714", # Consider merging multiple comparisons
+    "PLW",     # pylint warning
+    "PYI",     # flake8-pyi
+    "RUF",     # ruff
+    "T100",    # flake8-debugger
+    "UP",      # pyupgrade
+    "W",       # pycodestyle
+]
+lint.ignore = [
+    # bugbear ignore
+    "B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable.
+    "B007", # Loop control variable `i` not used within loop body
+    "B009", # Do not call `getattr` with a constant attribute value
+    "B010", # [*] Do not call `setattr` with a constant attribute value.
+    "B011", # Do not `assert False` (`python -O` removes these calls)
+    "B028", # No explicit `stacklevel` keyword argument found
+    # pydocstyle ignore
+    "D100", # Missing docstring in public module
+    "D101", # Missing docstring in public class
+    "D102", # Missing docstring in public method
+    "D103", # Missing docstring in public function
+    "D104", # Missing docstring in public package
+    "D105", # Missing docstring in magic method
+    "D106", # Missing docstring in public nested class
+    "D107", # Missing docstring in `__init__`
+    "D205", # 1 blank line required between summary line and description
+    "D209", # [*] Multi-line docstring closing quotes should be on a separate line
+    "D400", # First line should end with a period
+    "D401", # First line of docstring should be in imperative mood
+    "D402", # First line should not be the function's signature
+    "D404", # First word of the docstring should not be "This"
+    "D415", # First line should end with a period, question mark, or exclamation point
+    # pytest can do weird low-level things, and we usually know
+    # what we're doing when we use type(..) is ...
+    "E721", # Do not compare types, use `isinstance()`
+    # pylint ignore
+    "PLC0105", # `TypeVar` name "E" does not reflect its covariance;
+    "PLC0414", # Import alias does not rename original package
+    "PLR0124", # Name compared with itself
+    "PLR0133", # Two constants compared in a comparison (lots of those in tests)
+    "PLR0402", # Use `from x.y import z` in lieu of alias
+    "PLR0911", # Too many return statements
+    "PLR0912", # Too many branches
+    "PLR0913", # Too many arguments in function definition
+    "PLR0915", # Too many statements
+    "PLR2004", # Magic value used in comparison
+    "PLR2044", # Line with empty comment
+    "PLR5501", # Use `elif` instead of `else` then `if`
+    "PLW0120", # remove the else and dedent its contents
+    "PLW0603", # Using the global statement
+    "PLW2901", # for loop variable overwritten by assignment target
+    # ruff ignore
+    "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
+]
+lint.per-file-ignores."src/_pytest/_py/**/*.py" = [
+    "B",
+    "PYI",
+]
+lint.per-file-ignores."src/_pytest/_version.py" = [
+    "I001",
+]
+# can't be disabled on a line-by-line basis in file
+lint.per-file-ignores."testing/code/test_source.py" = [
+    "F841",
+]
+lint.per-file-ignores."testing/python/approx.py" = [
+    "B015",
+]
+lint.extend-safe-fixes = [
+    "UP006",
+    "UP007",
+]
+lint.isort.combine-as-imports = true
+lint.isort.force-single-line = true
+lint.isort.force-sort-within-sections = true
+lint.isort.known-local-folder = [
+    "pytest",
+    "_pytest",
+]
+lint.isort.lines-after-imports = 2
+lint.isort.order-by-type = false
+lint.isort.required-imports = [
+    "from __future__ import annotations",
+]
+# In order to be able to format for 88 char in ruff format
+lint.pycodestyle.max-line-length = 120
+lint.pydocstyle.convention = "pep257"
+lint.pyupgrade.keep-runtime-typing = false
+
+[tool.pylint.main]
+# Maximum number of characters on a single line.
+max-line-length = 120
+disable = [
+    "abstract-method",
+    "arguments-differ",
+    "arguments-renamed",
+    "assigning-non-slot",
+    "attribute-defined-outside-init",
+    "bad-builtin",
+    "bad-classmethod-argument",
+    "bad-dunder-name",
+    "bad-mcs-method-argument",
+    "broad-exception-caught",
+    "broad-exception-raised",
+    "cell-var-from-loop",                     # B023 from ruff / flake8-bugbear
+    "comparison-of-constants",                # disabled in ruff (PLR0133)
+    "comparison-with-callable",
+    "comparison-with-itself",                 # PLR0124 from ruff
+    "condition-evals-to-constant",
+    "consider-alternative-union-syntax",
+    "confusing-consecutive-elif",
+    "consider-using-assignment-expr",
+    "consider-using-dict-items",
+    "consider-using-from-import",
+    "consider-using-f-string",
+    "consider-using-in",
+    "consider-using-namedtuple-or-dataclass",
+    "consider-using-ternary",
+    "consider-using-tuple",
+    "consider-using-with",
+    "consider-using-from-import",             # not activated by default, PLR0402 disabled in ruff
+    "consider-ternary-expression",
+    "cyclic-import",
+    "differing-param-doc",
+    "docstring-first-line-empty",
+    "deprecated-argument",
+    "deprecated-attribute",
+    "deprecated-class",
+    "disallowed-name",                        # foo / bar are used often in tests
+    "duplicate-code",
+    "else-if-used",                           # not activated by default, PLR5501 disabled in ruff
+    "empty-comment",                          # not activated by default, PLR2044 disabled in ruff
+    "eval-used",
+    "eq-without-hash",
+    "exec-used",
+    "expression-not-assigned",
+    "fixme",
+    "global-statement",                       # PLW0603 disabled in ruff
+    "import-error",
+    "import-outside-toplevel",
+    "import-private-name",
+    "inconsistent-return-statements",
+    "invalid-bool-returned",
+    "invalid-name",
+    "invalid-repr-returned",
+    "invalid-str-returned",
+    "keyword-arg-before-vararg",
+    "line-too-long",
+    "magic-value-comparison",                 # not activated by default, PLR2004 disabled in ruff
+    "method-hidden",
+    "missing-docstring",
+    "missing-param-doc",
+    "missing-raises-doc",
+    "missing-timeout",
+    "missing-type-doc",
+    "misplaced-bare-raise",                   # PLE0704 from ruff
+    "misplaced-comparison-constant",
+    "multiple-statements",                    # multiple-statements-on-one-line-colon (E701) from ruff
+    "no-else-break",
+    "no-else-continue",
+    "no-else-raise",
+    "no-else-return",
+    "no-member",
+    "no-name-in-module",
+    "no-self-argument",
+    "no-self-use",
+    "not-an-iterable",
+    "not-callable",
+    "pointless-exception-statement",          # https://github.com/pytest-dev/pytest/pull/12379
+    "pointless-statement",                    # https://github.com/pytest-dev/pytest/pull/12379
+    "pointless-string-statement",             # https://github.com/pytest-dev/pytest/pull/12379
+    "possibly-used-before-assignment",
+    "protected-access",
+    "raise-missing-from",
+    "redefined-argument-from-local",
+    "redefined-builtin",
+    "redefined-loop-name",                    # PLW2901 disabled in ruff
+    "redefined-outer-name",
+    "redefined-variable-type",
+    "reimported",
+    "simplifiable-condition",
+    "simplifiable-if-expression",
+    "singleton-comparison",
+    "superfluous-parens",
+    "super-init-not-called",
+    "too-complex",
+    "too-few-public-methods",
+    "too-many-ancestors",
+    "too-many-arguments",                     # disabled in ruff
+    "too-many-branches",                      # disabled in ruff
+    "too-many-function-args",
+    "too-many-instance-attributes",
+    "too-many-lines",
+    "too-many-locals",
+    "too-many-nested-blocks",
+    "too-many-positional-arguments",
+    "too-many-public-methods",
+    "too-many-return-statements",             # disabled in ruff
+    "too-many-statements",                    # disabled in ruff
+    "too-many-try-statements",
+    "try-except-raise",
+    "typevar-name-incorrect-variance",        # PLC0105 disabled in ruff
+    "unbalanced-tuple-unpacking",
+    "undefined-loop-variable",
+    "undefined-variable",
+    "unexpected-keyword-arg",
+    "unidiomatic-typecheck",
+    "unnecessary-comprehension",
+    "unnecessary-dunder-call",
+    "unnecessary-lambda",
+    "unnecessary-lambda-assignment",
+    "unpacking-non-sequence",
+    "unspecified-encoding",
+    "unsubscriptable-object",
+    "unused-argument",
+    "unused-import",
+    "unused-variable",
+    "used-before-assignment",
+    "use-dict-literal",
+    "use-implicit-booleaness-not-comparison",
+    "use-implicit-booleaness-not-len",
+    "use-set-for-membership",
+    "useless-else-on-loop",                   # PLC0414 disabled in ruff
+    "useless-import-alias",
+    "useless-return",
+    "using-constant-test",
+    "while-used",
+    "wrong-import-order",                     # handled by isort / ruff
+    "wrong-import-position",                  # handled by isort / ruff
+]
+
+[tool.codespell]
+ignore-words-list = "afile,asend,asser,assertio,feld,hove,ned,noes,notin,paramete,parth,socio-economic,tesults,varius,wil"
+skip = "AUTHORS,*/plugin_list.rst"
+write-changes = true
+
+[tool.check-wheel-contents]
+# check-wheel-contents is executed by the build-and-inspect-python-package action.
+# W009: Wheel contains multiple toplevel library entries
+ignore = "W009"
+
+[tool.pyproject-fmt]
+indent = 4
+max_supported_python = "3.13"
+
 [tool.pytest.ini_options]
 minversion = "2.0"
 addopts = "-rfEX -p pytester --strict-markers"
-python_files = ["test_*.py", "*_test.py", "testing/python/*.py"]
-python_classes = ["Test", "Acceptance"]
-python_functions = ["test"]
+python_files = [
+    "test_*.py",
+    "*_test.py",
+    "testing/python/*.py",
+]
+python_classes = [
+    "Test",
+    "Acceptance",
+]
+python_functions = [
+    "test",
+]
 # NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
-testpaths = ["testing"]
-norecursedirs = ["testing/example_scripts"]
+testpaths = [
+    "testing",
+]
+norecursedirs = [
+    "testing/example_scripts",
+    ".*",
+    "build",
+    "dist",
+]
 xfail_strict = true
 filterwarnings = [
     "error",
@@ -28,8 +385,6 @@ filterwarnings = [
     "default:the imp module is deprecated in favour of importlib:DeprecationWarning:nose.*",
     # distutils is deprecated in 3.10, scheduled for removal in 3.12
     "ignore:The distutils package is deprecated:DeprecationWarning",
-    # produced by python3.6/site.py itself (3.6.7 on Travis, could not trigger it with 3.6.8)."
-    "ignore:.*U.*mode is deprecated:DeprecationWarning:(?!(pytest|_pytest))",
     # produced by pytest-xdist
     "ignore:.*type argument to addoption.*:DeprecationWarning",
     # produced on execnet (pytest-xdist)
@@ -40,6 +395,9 @@ filterwarnings = [
     # Those are caught/handled by pyupgrade, and not easy to filter with the
     # module being the filename (with .py removed).
     "default:invalid escape sequence:DeprecationWarning",
+    # ignore not yet fixed warnings for hook markers
+    "default:.*not marked using pytest.hook.*",
+    "ignore:.*not marked using pytest.hook.*::xdist.*",
     # ignore use of unregistered marks, because we use many to test the implementation
     "ignore::_pytest.warning_types.PytestUnknownMarkWarning",
     # https://github.com/benjaminp/six/issues/341
@@ -50,6 +408,9 @@ filterwarnings = [
     "ignore:VendorImporter\\.find_spec\\(\\) not found; falling back to find_module\\(\\):ImportWarning",
     # https://github.com/pytest-dev/execnet/pull/127
     "ignore:isSet\\(\\) is deprecated, use is_set\\(\\) instead:DeprecationWarning",
+    # https://github.com/pytest-dev/pytest/issues/2366
+    # https://github.com/pytest-dev/pytest/pull/13057
+    "default::pytest.PytestFDWarning",
 ]
 pytester_example_dir = "testing/example_scripts"
 markers = [
@@ -57,13 +418,15 @@ markers = [
     "foo",
     "bar",
     "baz",
+    "number_mark",
+    "builtin_matchers_mark",
+    "str_mark",
     # conftest.py reorders tests moving slow ones to the end of the list
     "slow",
     # experimental mark for all tests using pexpect
     "uses_pexpect",
 ]
 
-
 [tool.towncrier]
 package = "pytest"
 package_dir = "src"
@@ -72,45 +435,95 @@ directory = "changelog/"
 title_format = "pytest {version} ({project_date})"
 template = "changelog/_template.rst"
 
-  [[tool.towncrier.type]]
-  directory = "breaking"
-  name = "Breaking Changes"
-  showcontent = true
-
-  [[tool.towncrier.type]]
-  directory = "deprecation"
-  name = "Deprecations"
-  showcontent = true
-
-  [[tool.towncrier.type]]
-  directory = "feature"
-  name = "Features"
-  showcontent = true
-
-  [[tool.towncrier.type]]
-  directory = "improvement"
-  name = "Improvements"
-  showcontent = true
-
-  [[tool.towncrier.type]]
-  directory = "bugfix"
-  name = "Bug Fixes"
-  showcontent = true
-
-  [[tool.towncrier.type]]
-  directory = "vendor"
-  name = "Vendored Libraries"
-  showcontent = true
-
-  [[tool.towncrier.type]]
-  directory = "doc"
-  name = "Improved Documentation"
-  showcontent = true
-
-  [[tool.towncrier.type]]
-  directory = "trivial"
-  name = "Trivial/Internal Changes"
-  showcontent = true
+# NOTE: The types are declared because:
+# NOTE: - there is no mechanism to override just the value of
+# NOTE:   `tool.towncrier.type.misc.showcontent`;
+# NOTE: - and, we want to declare extra non-default types for
+# NOTE:   clarity and flexibility.
 
-[tool.black]
-target-version = ['py36']
+[[tool.towncrier.type]]
+# When something public gets removed in a breaking way. Could be
+# deprecated in an earlier release.
+directory = "breaking"
+name = "Removals and backward incompatible breaking changes"
+showcontent = true
+
+[[tool.towncrier.type]]
+# Declarations of future API removals and breaking changes in behavior.
+directory = "deprecation"
+name = "Deprecations (removal in next major release)"
+showcontent = true
+
+[[tool.towncrier.type]]
+# New behaviors, public APIs. That sort of stuff.
+directory = "feature"
+name = "New features"
+showcontent = true
+
+[[tool.towncrier.type]]
+# New behaviors in existing features.
+directory = "improvement"
+name = "Improvements in existing functionality"
+showcontent = true
+
+[[tool.towncrier.type]]
+# Something we deemed an improper undesired behavior that got corrected
+# in the release to match pre-agreed expectations.
+directory = "bugfix"
+name = "Bug fixes"
+showcontent = true
+
+[[tool.towncrier.type]]
+# Updates regarding bundling dependencies.
+directory = "vendor"
+name = "Vendored libraries"
+showcontent = true
+
+[[tool.towncrier.type]]
+# Notable updates to the documentation structure or build process.
+directory = "doc"
+name = "Improved documentation"
+showcontent = true
+
+[[tool.towncrier.type]]
+# Notes for downstreams about unobvious side effects and tooling. Changes
+# in the test invocation considerations and runtime assumptions.
+directory = "packaging"
+name = "Packaging updates and notes for downstreams"
+showcontent = true
+
+[[tool.towncrier.type]]
+# Stuff that affects the contributor experience. e.g. Running tests,
+# building the docs, setting up the development environment.
+directory = "contrib"
+name = "Contributor-facing changes"
+showcontent = true
+
+[[tool.towncrier.type]]
+# Changes that are hard to assign to any of the above categories.
+directory = "misc"
+name = "Miscellaneous internal changes"
+showcontent = true
+
+[tool.mypy]
+files = [
+    "src",
+    "testing",
+    "scripts",
+]
+mypy_path = [
+    "src",
+]
+python_version = "3.9"
+check_untyped_defs = true
+disallow_any_generics = true
+disallow_untyped_defs = true
+ignore_missing_imports = true
+show_error_codes = true
+strict_equality = true
+warn_redundant_casts = true
+warn_return_any = true
+warn_unreachable = true
+warn_unused_configs = true
+no_implicit_reexport = true
+warn_unused_ignores = true
diff --git a/scripts/.gitignore b/scripts/.gitignore
new file mode 100644
index 00000000000..50a75b62959
--- /dev/null
+++ b/scripts/.gitignore
@@ -0,0 +1 @@
+latest-release-notes.md
diff --git a/scripts/generate-gh-release-notes.py b/scripts/generate-gh-release-notes.py
new file mode 100644
index 00000000000..b6d92d085e1
--- /dev/null
+++ b/scripts/generate-gh-release-notes.py
@@ -0,0 +1,69 @@
+# mypy: disallow-untyped-defs
+"""
+Script used to generate a Markdown file containing only the changelog entries of a specific pytest release, which
+is then published as a GitHub Release during deploy (see workflows/deploy.yml).
+
+The script requires ``pandoc`` to be previously installed in the system -- we need to convert from RST (the format of
+our CHANGELOG) into Markdown (which is required by GitHub Releases).
+
+Requires Python3.6+.
+"""
+
+from __future__ import annotations
+
+from collections.abc import Sequence
+from pathlib import Path
+import re
+import sys
+
+import pypandoc
+
+
+def extract_changelog_entries_for(version: str) -> str:
+    p = Path(__file__).parent.parent / "doc/en/changelog.rst"
+    changelog_lines = p.read_text(encoding="UTF-8").splitlines()
+
+    title_regex = re.compile(r"pytest (\d\.\d+\.\d+\w*) \(\d{4}-\d{2}-\d{2}\)")
+    consuming_version = False
+    version_lines = []
+    for line in changelog_lines:
+        m = title_regex.match(line)
+        if m:
+            # Found the version we want: start to consume lines until we find the next version title.
+            if m.group(1) == version:
+                consuming_version = True
+            # Found a new version title while parsing the version we want: break out.
+            elif consuming_version:
+                break
+        if consuming_version:
+            version_lines.append(line)
+
+    return "\n".join(version_lines)
+
+
+def convert_rst_to_md(text: str) -> str:
+    result = pypandoc.convert_text(
+        text, "md", format="rst", extra_args=["--wrap=preserve"]
+    )
+    assert isinstance(result, str), repr(result)
+    return result
+
+
+def main(argv: Sequence[str]) -> int:
+    if len(argv) != 3:
+        print("Usage: generate-gh-release-notes VERSION FILE")
+        return 2
+
+    version, filename = argv[1:3]
+    print(f"Generating GitHub release notes for version {version}")
+    rst_body = extract_changelog_entries_for(version)
+    md_body = convert_rst_to_md(rst_body)
+    Path(filename).write_text(md_body, encoding="UTF-8")
+    print()
+    print(f"Done: {filename}")
+    print()
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main(sys.argv))
diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py
index f16e7b9625c..c420a80b3d2 100644
--- a/scripts/prepare-release-pr.py
+++ b/scripts/prepare-release-pr.py
@@ -1,3 +1,4 @@
+# mypy: disallow-untyped-defs
 """
 This script is part of the pytest release process which is triggered manually in the Actions
 tab of the repository.
@@ -9,19 +10,20 @@
 
 After that, it will create a release using the `release` tox environment, and push a new PR.
 
-**Token**: currently the token from the GitHub Actions is used, pushed with
-`pytest bot <pytestbot@gmail.com>` commit author.
+Note: the script uses the `gh` command-line tool, so `GH_TOKEN` must be set in the environment.
 """
+
+from __future__ import annotations
+
 import argparse
-import re
 from pathlib import Path
+import re
 from subprocess import check_call
 from subprocess import check_output
 from subprocess import run
 
 from colorama import Fore
 from colorama import init
-from github3.repos import Repository
 
 
 class InvalidFeatureRelease(Exception):
@@ -31,24 +33,26 @@ class InvalidFeatureRelease(Exception):
 SLUG = "pytest-dev/pytest"
 
 PR_BODY = """\
-Created automatically from manual trigger.
+Created by the [prepare release pr]\
+(https://github.com/pytest-dev/pytest/actions/workflows/prepare-release-pr.yml) workflow.
 
-Once all builds pass and it has been **approved** by one or more maintainers, the build
-can be released by pushing a tag `{version}` to this repository.
-"""
+Once all builds pass and it has been **approved** by one or more maintainers, start the \
+[deploy](https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml) workflow, using these parameters:
 
+* `Use workflow from`: `release-{version}`.
+* `Release version`: `{version}`.
 
-def login(token: str) -> Repository:
-    import github3
+Or execute on the command line:
 
-    github = github3.login(token=token)
-    owner, repo = SLUG.split("/")
-    return github.repository(owner, repo)
+```console
+gh workflow run deploy.yml -r release-{version} -f version={version}
+```
+
+After the workflow has been approved by a core maintainer, the package will be uploaded to PyPI automatically.
+"""
 
 
-def prepare_release_pr(
-    base_branch: str, is_major: bool, token: str, prerelease: str
-) -> None:
+def prepare_release_pr(base_branch: str, is_major: bool, prerelease: str) -> None:
     print()
     print(f"Processing release for branch {Fore.CYAN}{base_branch}")
 
@@ -66,7 +70,7 @@ def prepare_release_pr(
         )
     except InvalidFeatureRelease as e:
         print(f"{Fore.RED}{e}")
-        raise SystemExit(1)
+        raise SystemExit(1) from None
 
     print(f"Version: {Fore.CYAN}{version}")
 
@@ -88,7 +92,9 @@ def prepare_release_pr(
 
     print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.")
 
-    if prerelease:
+    if is_major:
+        template_name = "release.major.rst"
+    elif prerelease:
         template_name = "release.pre.rst"
     elif is_feature_release:
         template_name = "release.minor.rst"
@@ -104,6 +110,7 @@ def prepare_release_pr(
         "--",
         version,
         template_name,
+        release_branch,  # doc_version
         "--skip-check-links",
     ]
     print("Running", " ".join(cmdline))
@@ -112,22 +119,25 @@ def prepare_release_pr(
         check=True,
     )
 
-    oauth_url = f"https://{token}:x-oauth-basic@github.com/{SLUG}.git"
     run(
-        ["git", "push", oauth_url, f"HEAD:{release_branch}", "--force"],
+        ["git", "push", "origin", f"HEAD:{release_branch}", "--force"],
         check=True,
     )
     print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} pushed.")
 
     body = PR_BODY.format(version=version)
-    repo = login(token)
-    pr = repo.create_pull(
-        f"Prepare release {version}",
-        base=base_branch,
-        head=release_branch,
-        body=body,
+    run(
+        [
+            "gh",
+            "pr",
+            "new",
+            f"--base={base_branch}",
+            f"--head={release_branch}",
+            f"--title=Release {version}",
+            f"--body={body}",
+        ],
+        check=True,
     )
-    print(f"Pull request {Fore.CYAN}{pr.url}{Fore.RESET} created.")
 
 
 def find_next_version(
@@ -144,7 +154,7 @@ def find_next_version(
     last_version = valid_versions[-1]
 
     if is_major:
-        return f"{last_version[0]+1}.0.0{prerelease}"
+        return f"{last_version[0] + 1}.0.0{prerelease}"
     elif is_feature_release:
         return f"{last_version[0]}.{last_version[1] + 1}.0{prerelease}"
     else:
@@ -155,14 +165,12 @@ def main() -> None:
     init(autoreset=True)
     parser = argparse.ArgumentParser()
     parser.add_argument("base_branch")
-    parser.add_argument("token")
     parser.add_argument("--major", action="store_true", default=False)
     parser.add_argument("--prerelease", default="")
     options = parser.parse_args()
     prepare_release_pr(
         base_branch=options.base_branch,
         is_major=options.major,
-        token=options.token,
         prerelease=options.prerelease,
     )
 
diff --git a/scripts/publish-gh-release-notes.py b/scripts/publish-gh-release-notes.py
deleted file mode 100644
index 68cbd7adffd..00000000000
--- a/scripts/publish-gh-release-notes.py
+++ /dev/null
@@ -1,102 +0,0 @@
-"""
-Script used to publish GitHub release notes extracted from CHANGELOG.rst.
-
-This script is meant to be executed after a successful deployment in GitHub actions.
-
-Uses the following environment variables:
-
-* GIT_TAG: the name of the tag of the current commit.
-* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions.
-
-  Create one at:
-
-    https://github.com/settings/tokens
-
-  This token should be set in a secret in the repository, which is exposed as an
-  environment variable in the main.yml workflow file.
-
-The script also requires ``pandoc`` to be previously installed in the system.
-
-Requires Python3.6+.
-"""
-import os
-import re
-import sys
-from pathlib import Path
-
-import github3
-import pypandoc
-
-
-def publish_github_release(slug, token, tag_name, body):
-    github = github3.login(token=token)
-    owner, repo = slug.split("/")
-    repo = github.repository(owner, repo)
-    return repo.create_release(tag_name=tag_name, body=body)
-
-
-def parse_changelog(tag_name):
-    p = Path(__file__).parent.parent / "doc/en/changelog.rst"
-    changelog_lines = p.read_text(encoding="UTF-8").splitlines()
-
-    title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)")
-    consuming_version = False
-    version_lines = []
-    for line in changelog_lines:
-        m = title_regex.match(line)
-        if m:
-            # found the version we want: start to consume lines until we find the next version title
-            if m.group(1) == tag_name:
-                consuming_version = True
-            # found a new version title while parsing the version we want: break out
-            elif consuming_version:
-                break
-        if consuming_version:
-            version_lines.append(line)
-
-    return "\n".join(version_lines)
-
-
-def convert_rst_to_md(text):
-    return pypandoc.convert_text(
-        text, "md", format="rst", extra_args=["--wrap=preserve"]
-    )
-
-
-def main(argv):
-    if len(argv) > 1:
-        tag_name = argv[1]
-    else:
-        tag_name = os.environ.get("GITHUB_REF")
-        if not tag_name:
-            print("tag_name not given and $GITHUB_REF not set", file=sys.stderr)
-            return 1
-        if tag_name.startswith("refs/tags/"):
-            tag_name = tag_name[len("refs/tags/") :]
-
-    token = os.environ.get("GH_RELEASE_NOTES_TOKEN")
-    if not token:
-        print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr)
-        return 1
-
-    slug = os.environ.get("GITHUB_REPOSITORY")
-    if not slug:
-        print("GITHUB_REPOSITORY not set", file=sys.stderr)
-        return 1
-
-    rst_body = parse_changelog(tag_name)
-    md_body = convert_rst_to_md(rst_body)
-    if not publish_github_release(slug, token, tag_name, md_body):
-        print("Could not publish release notes:", file=sys.stderr)
-        print(md_body, file=sys.stderr)
-        return 5
-
-    print()
-    print(f"Release notes for {tag_name} published successfully:")
-    print(f"https://github.com/{slug}/releases/tag/{tag_name}")
-    print()
-    return 0
-
-
-if __name__ == "__main__":
-    sys.exit(main(sys.argv))
diff --git a/scripts/release.major.rst b/scripts/release.major.rst
new file mode 100644
index 00000000000..76e447f0c6d
--- /dev/null
+++ b/scripts/release.major.rst
@@ -0,0 +1,24 @@
+pytest-{version}
+=======================================
+
+The pytest team is proud to announce the {version} release!
+
+This release contains new features, improvements, bug fixes, and breaking changes, so users
+are encouraged to take a look at the CHANGELOG carefully:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+{contributors}
+
+Happy testing,
+The pytest Development Team
diff --git a/scripts/release.minor.rst b/scripts/release.minor.rst
index 76e447f0c6d..9a06d3d4140 100644
--- a/scripts/release.minor.rst
+++ b/scripts/release.minor.rst
@@ -3,8 +3,8 @@ pytest-{version}
 
 The pytest team is proud to announce the {version} release!
 
-This release contains new features, improvements, bug fixes, and breaking changes, so users
-are encouraged to take a look at the CHANGELOG carefully:
+This release contains new features, improvements, and bug fixes,
+the full list of changes is available in the changelog:
 
     https://docs.pytest.org/en/stable/changelog.html
 
diff --git a/scripts/release.patch.rst b/scripts/release.patch.rst
index 59fbe50ce0e..120cae51702 100644
--- a/scripts/release.patch.rst
+++ b/scripts/release.patch.rst
@@ -3,9 +3,7 @@ pytest-{version}
 
 pytest {version} has just been released to PyPI.
 
-This is a bug-fix release, being a drop-in replacement. To upgrade::
-
-  pip install --upgrade pytest
+This is a bug-fix release, being a drop-in replacement.
 
 The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
 
diff --git a/scripts/release.pre.rst b/scripts/release.pre.rst
index f0b054de3fc..960fae7e4f6 100644
--- a/scripts/release.pre.rst
+++ b/scripts/release.pre.rst
@@ -19,7 +19,7 @@ You can upgrade from PyPI via:
 
 Users are encouraged to take a look at the CHANGELOG carefully:
 
-    https://docs.pytest.org/en/stable/changelog.html
+    https://docs.pytest.org/en/{doc_version}/changelog.html
 
 Thanks to all the contributors to this release:
 
diff --git a/scripts/release.py b/scripts/release.py
index 19608991b61..aef5d6d5f73 100644
--- a/scripts/release.py
+++ b/scripts/release.py
@@ -1,7 +1,12 @@
+# mypy: disallow-untyped-defs
 """Invoke development tasks."""
+
+from __future__ import annotations
+
 import argparse
 import os
 from pathlib import Path
+import re
 from subprocess import call
 from subprocess import check_call
 from subprocess import check_output
@@ -10,19 +15,29 @@
 from colorama import init
 
 
-def announce(version, template_name):
+def announce(version: str, template_name: str, doc_version: str) -> None:
     """Generates a new release announcement entry in the docs."""
-    # Get our list of authors
-    stdout = check_output(["git", "describe", "--abbrev=0", "--tags"])
-    stdout = stdout.decode("utf-8")
+    # Get our list of authors and co-authors.
+    stdout = check_output(["git", "describe", "--abbrev=0", "--tags"], encoding="UTF-8")
     last_version = stdout.strip()
+    rev_range = f"{last_version}..HEAD"
 
-    stdout = check_output(["git", "log", f"{last_version}..HEAD", "--format=%aN"])
-    stdout = stdout.decode("utf-8")
+    authors = check_output(
+        ["git", "log", rev_range, "--format=%aN"], encoding="UTF-8"
+    ).splitlines()
+
+    co_authors_output = check_output(
+        ["git", "log", rev_range, "--format=%(trailers:key=Co-authored-by) "],
+        encoding="UTF-8",
+    )
+    co_authors: list[str] = []
+    for co_author_line in co_authors_output.splitlines():
+        if m := re.search(r"Co-authored-by: (.+?)<", co_author_line):
+            co_authors.append(m.group(1).strip())
 
     contributors = {
         name
-        for name in stdout.splitlines()
+        for name in authors + co_authors
         if not name.endswith("[bot]") and name != "pytest bot"
     }
 
@@ -31,7 +46,9 @@ def announce(version, template_name):
     )
 
     contributors_text = "\n".join(f"* {name}" for name in sorted(contributors)) + "\n"
-    text = template_text.format(version=version, contributors=contributors_text)
+    text = template_text.format(
+        version=version, contributors=contributors_text, doc_version=doc_version
+    )
 
     target = Path(__file__).parent.joinpath(f"../doc/en/announce/release-{version}.rst")
     target.write_text(text, encoding="UTF-8")
@@ -59,7 +76,7 @@ def announce(version, template_name):
     check_call(["git", "add", str(target)])
 
 
-def regen(version):
+def regen(version: str) -> None:
     """Call regendoc tool to update examples and pytest output in the docs."""
     print(f"{Fore.CYAN}[generate.regen] {Fore.RESET}Updating docs")
     check_call(
@@ -68,7 +85,7 @@ def regen(version):
     )
 
 
-def fix_formatting():
+def fix_formatting() -> None:
     """Runs pre-commit in all files to ensure they are formatted correctly"""
     print(
         f"{Fore.CYAN}[generate.fix linting] {Fore.RESET}Fixing formatting using pre-commit"
@@ -76,15 +93,17 @@ def fix_formatting():
     call(["pre-commit", "run", "--all-files"])
 
 
-def check_links():
+def check_links() -> None:
     """Runs sphinx-build to check links"""
     print(f"{Fore.CYAN}[generate.check_links] {Fore.RESET}Checking links")
     check_call(["tox", "-e", "docs-checklinks"])
 
 
-def pre_release(version, template_name, *, skip_check_links):
+def pre_release(
+    version: str, template_name: str, doc_version: str, *, skip_check_links: bool
+) -> None:
     """Generates new docs, release announcements and creates a local tag."""
-    announce(version, template_name)
+    announce(version, template_name, doc_version)
     regen(version)
     changelog(version, write_out=True)
     fix_formatting()
@@ -100,23 +119,27 @@ def pre_release(version, template_name, *, skip_check_links):
     print("Please push your branch and open a PR.")
 
 
-def changelog(version, write_out=False):
+def changelog(version: str, write_out: bool = False) -> None:
     addopts = [] if write_out else ["--draft"]
-    check_call(["towncrier", "--yes", "--version", version] + addopts)
+    check_call(["towncrier", "--yes", "--version", version, *addopts])
 
 
-def main():
+def main() -> None:
     init(autoreset=True)
     parser = argparse.ArgumentParser()
     parser.add_argument("version", help="Release version")
     parser.add_argument(
         "template_name", help="Name of template file to use for release announcement"
     )
+    parser.add_argument(
+        "doc_version", help="For prereleases, the version to link to in the docs"
+    )
     parser.add_argument("--skip-check-links", action="store_true", default=False)
     options = parser.parse_args()
     pre_release(
         options.version,
         options.template_name,
+        options.doc_version,
         skip_check_links=options.skip_check_links,
     )
 
diff --git a/scripts/towncrier-draft-to-file.py b/scripts/towncrier-draft-to-file.py
deleted file mode 100644
index 81507b40b75..00000000000
--- a/scripts/towncrier-draft-to-file.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import sys
-from subprocess import call
-
-
-def main():
-    """
-    Platform agnostic wrapper script for towncrier.
-    Fixes the issue (#7251) where windows users are unable to natively run tox -e docs to build pytest docs.
-    """
-    with open("doc/en/_changelog_towncrier_draft.rst", "w") as draft_file:
-        return call(("towncrier", "--draft"), stdout=draft_file)
-
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/scripts/update-plugin-list.py b/scripts/update-plugin-list.py
index c034c72420b..61debb44043 100644
--- a/scripts/update-plugin-list.py
+++ b/scripts/update-plugin-list.py
@@ -1,23 +1,51 @@
+# mypy: disallow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Iterable
+from collections.abc import Iterator
 import datetime
 import pathlib
 import re
 from textwrap import dedent
 from textwrap import indent
+from typing import Any
+from typing import TypedDict
 
 import packaging.version
-import requests
+import platformdirs
+from requests_cache import CachedResponse
+from requests_cache import CachedSession
+from requests_cache import OriginalResponse
+from requests_cache import SQLiteCache
 import tabulate
-import wcwidth
 from tqdm import tqdm
+import wcwidth
+
 
 FILE_HEAD = r"""
+.. Note this file is autogenerated by scripts/update-plugin-list.py - usually weekly via github action
+
 .. _plugin-list:
 
-Plugin List
-===========
+Pytest Plugin List
+==================
+
+Below is an automated compilation of ``pytest``` plugins available on `PyPI <https://pypi.org>`_.
+It includes PyPI projects whose names begin with ``pytest-`` or ``pytest_`` and a handful of manually selected projects.
+Packages classified as inactive are excluded.
+
+For detailed insights into how this list is generated,
+please refer to `the update script <https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py>`_.
+
+.. warning::
+
+   Please be aware that this list is not a curated collection of projects
+   and does not undergo a systematic review process.
+   It serves purely as an informational resource to aid in the discovery of ``pytest`` plugins.
+
+   Do not presume any endorsement from the ``pytest`` project or its developers,
+   and always conduct your own quality assessment before incorporating any of these plugins into your own projects.
 
-PyPI projects that match "pytest-\*" are considered plugins and are listed
-automatically. Packages classified as inactive are excluded.
 
 .. The following conditional uses a different format for this list when
    creating a PDF, because otherwise the table gets far too wide for the
@@ -33,6 +61,14 @@
     "Development Status :: 6 - Mature",
     "Development Status :: 7 - Inactive",
 )
+ADDITIONAL_PROJECTS = {  # set of additional projects to consider as plugins
+    "logassert",
+    "logot",
+    "nuts",
+    "flask_fixture",
+    "databricks-labs-pytester",
+    "tursu",
+}
 
 
 def escape_rst(text: str) -> str:
@@ -48,22 +84,62 @@ def escape_rst(text: str) -> str:
     return text
 
 
-def iter_plugins():
-    regex = r">([\d\w-]*)</a>"
-    response = requests.get("https://pypi.org/simple")
+def project_response_with_refresh(
+    session: CachedSession, name: str, last_serial: int
+) -> OriginalResponse | CachedResponse:
+    """Get a http cached pypi project
+
+    force refresh in case of last serial mismatch
+    """
+    response = session.get(f"https://pypi.org/pypi/{name}/json")
+    if int(response.headers.get("X-PyPI-Last-Serial", -1)) != last_serial:
+        response = session.get(f"https://pypi.org/pypi/{name}/json", refresh=True)
+    return response
+
+
+def get_session() -> CachedSession:
+    """Configures the requests-cache session"""
+    cache_path = platformdirs.user_cache_path("pytest-plugin-list")
+    cache_path.mkdir(exist_ok=True, parents=True)
+    cache_file = cache_path.joinpath("http_cache.sqlite3")
+    return CachedSession(backend=SQLiteCache(cache_file))
 
-    matches = list(
-        match
-        for match in re.finditer(regex, response.text)
-        if match.groups()[0].startswith("pytest-")
+
+def pytest_plugin_projects_from_pypi(session: CachedSession) -> dict[str, int]:
+    response = session.get(
+        "https://pypi.org/simple",
+        headers={"Accept": "application/vnd.pypi.simple.v1+json"},
+        refresh=True,
     )
+    return {
+        name: p["_last-serial"]
+        for p in response.json()["projects"]
+        if (
+            (name := p["name"]).startswith(("pytest-", "pytest_"))
+            or name in ADDITIONAL_PROJECTS
+        )
+    }
+
+
+class PluginInfo(TypedDict):
+    """Relevant information about a plugin to generate the summary."""
+
+    name: str
+    summary: str
+    last_release: str
+    status: str
+    requires: str
+
 
-    for match in tqdm(matches, smoothing=0):
-        name = match.groups()[0]
-        response = requests.get(f"https://pypi.org/pypi/{name}/json")
+def iter_plugins() -> Iterator[PluginInfo]:
+    session = get_session()
+    name_2_serial = pytest_plugin_projects_from_pypi(session)
+
+    for name, last_serial in tqdm(name_2_serial.items(), smoothing=0):
+        response = project_response_with_refresh(session, name, last_serial)
         if response.status_code == 404:
-            # Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple but
-            # return 404 on the JSON API. Skip.
+            # Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple
+            # but return 404 on the JSON API. Skip.
             continue
         response.raise_for_status()
         info = response.json()["info"]
@@ -78,36 +154,49 @@ def iter_plugins():
         requires = "N/A"
         if info["requires_dist"]:
             for requirement in info["requires_dist"]:
-                if requirement == "pytest" or "pytest " in requirement:
+                if re.match(r"pytest(?![-.\w])", requirement):
                     requires = requirement
                     break
+
+        def version_sort_key(version_string: str) -> Any:
+            """
+            Return the sort key for the given version string
+            returned by the API.
+            """
+            try:
+                return packaging.version.parse(version_string)
+            except packaging.version.InvalidVersion:
+                # Use a hard-coded pre-release version.
+                return packaging.version.Version("0.0.0alpha")
+
         releases = response.json()["releases"]
-        for release in sorted(releases, key=packaging.version.parse, reverse=True):
+        for release in sorted(releases, key=version_sort_key, reverse=True):
             if releases[release]:
                 release_date = datetime.date.fromisoformat(
                     releases[release][-1]["upload_time_iso_8601"].split("T")[0]
                 )
                 last_release = release_date.strftime("%b %d, %Y")
                 break
-        name = f':pypi:`{info["name"]}`'
-        summary = escape_rst(info["summary"].replace("\n", ""))
+        name = f":pypi:`{info['name']}`"
+        summary = ""
+        if info["summary"]:
+            summary = escape_rst(info["summary"].replace("\n", ""))
         yield {
             "name": name,
             "summary": summary.strip(),
-            "last release": last_release,
+            "last_release": last_release,
             "status": status,
             "requires": requires,
         }
 
 
-def plugin_definitions(plugins):
+def plugin_definitions(plugins: Iterable[PluginInfo]) -> Iterator[str]:
     """Return RST for the plugin list that fits better on a vertical page."""
-
     for plugin in plugins:
         yield dedent(
             f"""
-            {plugin['name']}
-               *last release*: {plugin["last release"]},
+            {plugin["name"]}
+               *last release*: {plugin["last_release"]},
                *status*: {plugin["status"]},
                *requires*: {plugin["requires"]}
 
@@ -116,18 +205,18 @@ def plugin_definitions(plugins):
         )
 
 
-def main():
-    plugins = list(iter_plugins())
+def main() -> None:
+    plugins = [*iter_plugins()]
 
     reference_dir = pathlib.Path("doc", "en", "reference")
 
     plugin_list = reference_dir / "plugin_list.rst"
-    with plugin_list.open("w") as f:
+    with plugin_list.open("w", encoding="UTF-8") as f:
         f.write(FILE_HEAD)
         f.write(f"This list contains {len(plugins)} plugins.\n\n")
         f.write(".. only:: not latex\n\n")
 
-        wcwidth  # reference library that must exist for tabulate to work
+        _ = wcwidth  # reference library that must exist for tabulate to work
         plugin_table = tabulate.tabulate(plugins, headers="keys", tablefmt="rst")
         f.write(indent(plugin_table, "   "))
         f.write("\n\n")
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 26a5d2e63e5..00000000000
--- a/setup.cfg
+++ /dev/null
@@ -1,105 +0,0 @@
-[metadata]
-name = pytest
-description = pytest: simple powerful testing with Python
-long_description = file: README.rst
-long_description_content_type = text/x-rst
-url = https://docs.pytest.org/en/latest/
-author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
-license = MIT
-license_file = LICENSE
-platforms = unix, linux, osx, cygwin, win32
-classifiers =
-    Development Status :: 6 - Mature
-    Intended Audience :: Developers
-    License :: OSI Approved :: MIT License
-    Operating System :: MacOS :: MacOS X
-    Operating System :: Microsoft :: Windows
-    Operating System :: POSIX
-    Programming Language :: Python :: 3
-    Programming Language :: Python :: 3 :: Only
-    Programming Language :: Python :: 3.6
-    Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
-    Programming Language :: Python :: 3.9
-    Programming Language :: Python :: 3.10
-    Topic :: Software Development :: Libraries
-    Topic :: Software Development :: Testing
-    Topic :: Utilities
-keywords = test, unittest
-project_urls =
-    Changelog=https://docs.pytest.org/en/stable/changelog.html
-    Twitter=https://twitter.com/pytestdotorg
-    Source=https://github.com/pytest-dev/pytest
-    Tracker=https://github.com/pytest-dev/pytest/issues
-
-[options]
-packages =
-    _pytest
-    _pytest._code
-    _pytest._io
-    _pytest.assertion
-    _pytest.config
-    _pytest.mark
-    pytest
-install_requires =
-    attrs>=19.2.0
-    iniconfig
-    packaging
-    pluggy>=0.12,<2.0
-    py>=1.8.2
-    tomli>=1.0.0
-    atomicwrites>=1.0;sys_platform=="win32"
-    colorama;sys_platform=="win32"
-    importlib-metadata>=0.12;python_version<"3.8"
-python_requires = >=3.6
-package_dir =
-    =src
-setup_requires =
-    setuptools
-    setuptools-scm>=6.0
-zip_safe = no
-
-[options.entry_points]
-console_scripts =
-    pytest=pytest:console_main
-    py.test=pytest:console_main
-
-[options.extras_require]
-testing =
-    argcomplete
-    hypothesis>=3.56
-    mock
-    nose
-    pygments>=2.7.2
-    requests
-    xmlschema
-
-[options.package_data]
-_pytest = py.typed
-pytest = py.typed
-
-[build_sphinx]
-source_dir = doc/en/
-build_dir = doc/build
-all_files = 1
-
-[check-manifest]
-ignore =
-    src/_pytest/_version.py
-
-[devpi:upload]
-formats = sdist.tgz,bdist_wheel
-
-[mypy]
-mypy_path = src
-check_untyped_defs = True
-disallow_any_generics = True
-ignore_missing_imports = True
-no_implicit_optional = True
-show_error_codes = True
-strict_equality = True
-warn_redundant_casts = True
-warn_return_any = True
-warn_unreachable = True
-warn_unused_configs = True
-no_implicit_reexport = True
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 7f1a1763ca9..00000000000
--- a/setup.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from setuptools import setup
-
-if __name__ == "__main__":
-    setup()
diff --git a/src/_pytest/__init__.py b/src/_pytest/__init__.py
index 8a406c5c751..8eb8ec9605c 100644
--- a/src/_pytest/__init__.py
+++ b/src/_pytest/__init__.py
@@ -1,9 +1,13 @@
+from __future__ import annotations
+
+
 __all__ = ["__version__", "version_tuple"]
 
 try:
-    from ._version import version as __version__, version_tuple
+    from ._version import version as __version__
+    from ._version import version_tuple
 except ImportError:  # pragma: no cover
     # broken installation, we don't even try
     # unknown only works because we do poor mans version compare
     __version__ = "unknown"
-    version_tuple = (0, 0, "unknown")  # type:ignore[assignment]
+    version_tuple = (0, 0, "unknown")
diff --git a/src/_pytest/_argcomplete.py b/src/_pytest/_argcomplete.py
index 41d9d9407c7..59426ef949e 100644
--- a/src/_pytest/_argcomplete.py
+++ b/src/_pytest/_argcomplete.py
@@ -61,13 +61,14 @@
   which should throw a KeyError: 'COMPLINE' (which is properly set by the
   global argcomplete script).
 """
+
+from __future__ import annotations
+
 import argparse
+from glob import glob
 import os
 import sys
-from glob import glob
 from typing import Any
-from typing import List
-from typing import Optional
 
 
 class FastFilesCompleter:
@@ -76,17 +77,17 @@ class FastFilesCompleter:
     def __init__(self, directories: bool = True) -> None:
         self.directories = directories
 
-    def __call__(self, prefix: str, **kwargs: Any) -> List[str]:
+    def __call__(self, prefix: str, **kwargs: Any) -> list[str]:
         # Only called on non option completions.
-        if os.path.sep in prefix[1:]:
-            prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
+        if os.sep in prefix[1:]:
+            prefix_dir = len(os.path.dirname(prefix) + os.sep)
         else:
             prefix_dir = 0
         completion = []
         globbed = []
         if "*" not in prefix and "?" not in prefix:
             # We are on unix, otherwise no bash.
-            if not prefix or prefix[-1] == os.path.sep:
+            if not prefix or prefix[-1] == os.sep:
                 globbed.extend(glob(prefix + ".*"))
             prefix += "*"
         globbed.extend(glob(prefix))
@@ -103,12 +104,11 @@ def __call__(self, prefix: str, **kwargs: Any) -> List[str]:
         import argcomplete.completers
     except ImportError:
         sys.exit(-1)
-    filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter()
+    filescompleter: FastFilesCompleter | None = FastFilesCompleter()
 
     def try_argcomplete(parser: argparse.ArgumentParser) -> None:
         argcomplete.autocomplete(parser, always_complete_options=False)
 
-
 else:
 
     def try_argcomplete(parser: argparse.ArgumentParser) -> None:
diff --git a/src/_pytest/_code/__init__.py b/src/_pytest/_code/__init__.py
index 511d0dde661..7f67a2e3e0a 100644
--- a/src/_pytest/_code/__init__.py
+++ b/src/_pytest/_code/__init__.py
@@ -1,4 +1,7 @@
 """Python inspection/code generation API."""
+
+from __future__ import annotations
+
 from .code import Code
 from .code import ExceptionInfo
 from .code import filter_traceback
@@ -9,14 +12,15 @@
 from .source import getrawcode
 from .source import Source
 
+
 __all__ = [
     "Code",
     "ExceptionInfo",
-    "filter_traceback",
     "Frame",
-    "getfslineno",
-    "getrawcode",
+    "Source",
     "Traceback",
     "TracebackEntry",
-    "Source",
+    "filter_traceback",
+    "getfslineno",
+    "getrawcode",
 ]
diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py
index b19ee7c64d9..2c872df3008 100644
--- a/src/_pytest/_code/code.py
+++ b/src/_pytest/_code/code.py
@@ -1,37 +1,36 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import ast
+from collections.abc import Callable
+from collections.abc import Iterable
+from collections.abc import Mapping
+from collections.abc import Sequence
+import dataclasses
 import inspect
-import re
-import sys
-import traceback
 from inspect import CO_VARARGS
 from inspect import CO_VARKEYWORDS
 from io import StringIO
+import os
 from pathlib import Path
+import re
+import sys
+import traceback
 from traceback import format_exception_only
 from types import CodeType
 from types import FrameType
 from types import TracebackType
 from typing import Any
-from typing import Callable
 from typing import ClassVar
-from typing import Dict
+from typing import Final
+from typing import final
 from typing import Generic
-from typing import Iterable
-from typing import List
-from typing import Mapping
-from typing import Optional
+from typing import Literal
 from typing import overload
-from typing import Pattern
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
+from typing import SupportsIndex
 from typing import TypeVar
 from typing import Union
-from weakref import ref
 
-import attr
 import pluggy
 
 import _pytest
@@ -42,18 +41,18 @@
 from _pytest._io import TerminalWriter
 from _pytest._io.saferepr import safeformat
 from _pytest._io.saferepr import saferepr
-from _pytest.compat import final
 from _pytest.compat import get_real_func
 from _pytest.deprecated import check_ispytest
 from _pytest.pathlib import absolutepath
 from _pytest.pathlib import bestrelpath
 
-if TYPE_CHECKING:
-    from typing_extensions import Literal
-    from typing_extensions import SupportsIndex
-    from weakref import ReferenceType
 
-    _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
+if sys.version_info < (3, 11):
+    from exceptiongroup import BaseExceptionGroup
+
+TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
+
+EXCEPTION_OR_MORE = Union[type[BaseException], tuple[type[BaseException], ...]]
 
 
 class Code:
@@ -65,7 +64,7 @@ def __init__(self, obj: CodeType) -> None:
         self.raw = obj
 
     @classmethod
-    def from_function(cls, obj: object) -> "Code":
+    def from_function(cls, obj: object) -> Code:
         return cls(getrawcode(obj))
 
     def __eq__(self, other):
@@ -83,7 +82,7 @@ def name(self) -> str:
         return self.raw.co_name
 
     @property
-    def path(self) -> Union[Path, str]:
+    def path(self) -> Path | str:
         """Return a path object pointing to source code, or an ``str`` in
         case of ``OSError`` / non-existing file."""
         if not self.raw.co_filename:
@@ -100,17 +99,17 @@ def path(self) -> Union[Path, str]:
             return self.raw.co_filename
 
     @property
-    def fullsource(self) -> Optional["Source"]:
+    def fullsource(self) -> Source | None:
         """Return a _pytest._code.Source object for the full source file of the code."""
         full, _ = findsource(self.raw)
         return full
 
-    def source(self) -> "Source":
+    def source(self) -> Source:
         """Return a _pytest._code.Source object for the code object's source only."""
         # return source only for that part of code
         return Source(self.raw)
 
-    def getargs(self, var: bool = False) -> Tuple[str, ...]:
+    def getargs(self, var: bool = False) -> tuple[str, ...]:
         """Return a tuple with the argument names for the code object.
 
         If 'var' is set True also return the names of the variable and
@@ -139,11 +138,11 @@ def lineno(self) -> int:
         return self.raw.f_lineno - 1
 
     @property
-    def f_globals(self) -> Dict[str, Any]:
+    def f_globals(self) -> dict[str, Any]:
         return self.raw.f_globals
 
     @property
-    def f_locals(self) -> Dict[str, Any]:
+    def f_locals(self) -> dict[str, Any]:
         return self.raw.f_locals
 
     @property
@@ -151,7 +150,7 @@ def code(self) -> Code:
         return Code(self.raw.f_code)
 
     @property
-    def statement(self) -> "Source":
+    def statement(self) -> Source:
         """Statement this frame is at."""
         if self.code.fullsource is None:
             return Source("")
@@ -190,24 +189,63 @@ def getargs(self, var: bool = False):
 class TracebackEntry:
     """A single entry in a Traceback."""
 
-    __slots__ = ("_rawentry", "_excinfo", "_repr_style")
+    __slots__ = ("_rawentry", "_repr_style")
 
     def __init__(
         self,
         rawentry: TracebackType,
-        excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None,
+        repr_style: Literal["short", "long"] | None = None,
     ) -> None:
-        self._rawentry = rawentry
-        self._excinfo = excinfo
-        self._repr_style: Optional['Literal["short", "long"]'] = None
+        self._rawentry: Final = rawentry
+        self._repr_style: Final = repr_style
+
+    def with_repr_style(
+        self, repr_style: Literal["short", "long"] | None
+    ) -> TracebackEntry:
+        return TracebackEntry(self._rawentry, repr_style)
 
     @property
     def lineno(self) -> int:
         return self._rawentry.tb_lineno - 1
 
-    def set_repr_style(self, mode: "Literal['short', 'long']") -> None:
-        assert mode in ("short", "long")
-        self._repr_style = mode
+    def get_python_framesummary(self) -> traceback.FrameSummary:
+        # Python's built-in traceback module implements all the nitty gritty
+        # details to get column numbers of out frames.
+        stack_summary = traceback.extract_tb(self._rawentry, limit=1)
+        return stack_summary[0]
+
+    # Column and end line numbers introduced in python 3.11
+    if sys.version_info < (3, 11):
+
+        @property
+        def end_lineno_relative(self) -> int | None:
+            return None
+
+        @property
+        def colno(self) -> int | None:
+            return None
+
+        @property
+        def end_colno(self) -> int | None:
+            return None
+    else:
+
+        @property
+        def end_lineno_relative(self) -> int | None:
+            frame_summary = self.get_python_framesummary()
+            if frame_summary.end_lineno is None:  # pragma: no cover
+                return None
+            return frame_summary.end_lineno - 1 - self.frame.code.firstlineno
+
+        @property
+        def colno(self) -> int | None:
+            """Starting byte offset of the expression in the traceback entry."""
+            return self.get_python_framesummary().colno
+
+        @property
+        def end_colno(self) -> int | None:
+            """Ending byte offset of the expression in the traceback entry."""
+            return self.get_python_framesummary().end_colno
 
     @property
     def frame(self) -> Frame:
@@ -218,22 +256,22 @@ def relline(self) -> int:
         return self.lineno - self.frame.code.firstlineno
 
     def __repr__(self) -> str:
-        return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
+        return f"<TracebackEntry {self.frame.code.path}:{self.lineno + 1}>"
 
     @property
-    def statement(self) -> "Source":
+    def statement(self) -> Source:
         """_pytest._code.Source object for the current statement."""
         source = self.frame.code.fullsource
         assert source is not None
         return source.getstatement(self.lineno)
 
     @property
-    def path(self) -> Union[Path, str]:
+    def path(self) -> Path | str:
         """Path to the source code."""
         return self.frame.code.path
 
     @property
-    def locals(self) -> Dict[str, Any]:
+    def locals(self) -> dict[str, Any]:
         """Locals of underlying frame."""
         return self.frame.f_locals
 
@@ -241,8 +279,8 @@ def getfirstlinesource(self) -> int:
         return self.frame.code.firstlineno
 
     def getsource(
-        self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None
-    ) -> Optional["Source"]:
+        self, astcache: dict[str | Path, ast.AST] | None = None
+    ) -> Source | None:
         """Return failing source code."""
         # we use the passed in astcache to not reparse asttrees
         # within exception info printing
@@ -268,7 +306,7 @@ def getsource(
 
     source = property(getsource)
 
-    def ishidden(self) -> bool:
+    def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool:
         """Return True if the current frame has a var __tracebackhide__
         resolving to True.
 
@@ -277,9 +315,7 @@ def ishidden(self) -> bool:
 
         Mostly for internal use.
         """
-        tbh: Union[
-            bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]
-        ] = False
+        tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False
         for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals):
             # in normal cases, f_locals and f_globals are dictionaries
             # however via `exec(...)` / `eval(...)` they can be other types
@@ -292,7 +328,7 @@ def ishidden(self) -> bool:
             else:
                 break
         if tbh and callable(tbh):
-            return tbh(None if self._excinfo is None else self._excinfo())
+            return tbh(excinfo)
         return tbh
 
     def __str__(self) -> str:
@@ -306,12 +342,7 @@ def __str__(self) -> str:
         # This output does not quite match Python's repr for traceback entries,
         # but changing it to do so would break certain plugins.  See
         # https://github.com/pytest-dev/pytest/pull/7535/ for details.
-        return "  File %r:%d in %s\n  %s\n" % (
-            str(self.path),
-            self.lineno + 1,
-            name,
-            line,
-        )
+        return f"  File '{self.path}':{self.lineno + 1} in {name}\n  {line}\n"
 
     @property
     def name(self) -> str:
@@ -319,22 +350,20 @@ def name(self) -> str:
         return self.frame.code.raw.co_name
 
 
-class Traceback(List[TracebackEntry]):
+class Traceback(list[TracebackEntry]):
     """Traceback objects encapsulate and offer higher level access to Traceback entries."""
 
     def __init__(
         self,
-        tb: Union[TracebackType, Iterable[TracebackEntry]],
-        excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None,
+        tb: TracebackType | Iterable[TracebackEntry],
     ) -> None:
         """Initialize from given python traceback object and ExceptionInfo."""
-        self._excinfo = excinfo
         if isinstance(tb, TracebackType):
 
             def f(cur: TracebackType) -> Iterable[TracebackEntry]:
-                cur_: Optional[TracebackType] = cur
+                cur_: TracebackType | None = cur
                 while cur_ is not None:
-                    yield TracebackEntry(cur_, excinfo=excinfo)
+                    yield TracebackEntry(cur_)
                     cur_ = cur_.tb_next
 
             super().__init__(f(tb))
@@ -343,11 +372,11 @@ def f(cur: TracebackType) -> Iterable[TracebackEntry]:
 
     def cut(
         self,
-        path: Optional[Union[Path, str]] = None,
-        lineno: Optional[int] = None,
-        firstlineno: Optional[int] = None,
-        excludepath: Optional[Path] = None,
-    ) -> "Traceback":
+        path: os.PathLike[str] | str | None = None,
+        lineno: int | None = None,
+        firstlineno: int | None = None,
+        excludepath: os.PathLike[str] | None = None,
+    ) -> Traceback:
         """Return a Traceback instance wrapping part of this Traceback.
 
         By providing any combination of path, lineno and firstlineno, the
@@ -357,103 +386,125 @@ def cut(
         for formatting reasons (removing some uninteresting bits that deal
         with handling of the exception/traceback).
         """
+        path_ = None if path is None else os.fspath(path)
+        excludepath_ = None if excludepath is None else os.fspath(excludepath)
         for x in self:
             code = x.frame.code
             codepath = code.path
-            if path is not None and codepath != path:
+            if path is not None and str(codepath) != path_:
                 continue
             if (
                 excludepath is not None
                 and isinstance(codepath, Path)
-                and excludepath in codepath.parents
+                and excludepath_ in (str(p) for p in codepath.parents)  # type: ignore[operator]
             ):
                 continue
             if lineno is not None and x.lineno != lineno:
                 continue
             if firstlineno is not None and x.frame.code.firstlineno != firstlineno:
                 continue
-            return Traceback(x._rawentry, self._excinfo)
+            return Traceback(x._rawentry)
         return self
 
     @overload
-    def __getitem__(self, key: "SupportsIndex") -> TracebackEntry:
-        ...
+    def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ...
 
     @overload
-    def __getitem__(self, key: slice) -> "Traceback":
-        ...
+    def __getitem__(self, key: slice) -> Traceback: ...
 
-    def __getitem__(
-        self, key: Union["SupportsIndex", slice]
-    ) -> Union[TracebackEntry, "Traceback"]:
+    def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback:
         if isinstance(key, slice):
             return self.__class__(super().__getitem__(key))
         else:
             return super().__getitem__(key)
 
     def filter(
-        self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden()
-    ) -> "Traceback":
-        """Return a Traceback instance with certain items removed
+        self,
+        excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool],
+        /,
+    ) -> Traceback:
+        """Return a Traceback instance with certain items removed.
 
-        fn is a function that gets a single argument, a TracebackEntry
-        instance, and should return True when the item should be added
-        to the Traceback, False when not.
+        If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s
+        which are hidden (see ishidden() above).
 
-        By default this removes all the TracebackEntries which are hidden
-        (see ishidden() above).
+        Otherwise, the filter is a function that gets a single argument, a
+        ``TracebackEntry`` instance, and should return True when the item should
+        be added to the ``Traceback``, False when not.
         """
-        return Traceback(filter(fn, self), self._excinfo)
-
-    def getcrashentry(self) -> TracebackEntry:
-        """Return last non-hidden traceback entry that lead to the exception of a traceback."""
-        for i in range(-1, -len(self) - 1, -1):
-            entry = self[i]
-            if not entry.ishidden():
-                return entry
-        return self[-1]
+        if isinstance(excinfo_or_fn, ExceptionInfo):
+            fn = lambda x: not x.ishidden(excinfo_or_fn)  # noqa: E731
+        else:
+            fn = excinfo_or_fn
+        return Traceback(filter(fn, self))
 
-    def recursionindex(self) -> Optional[int]:
+    def recursionindex(self) -> int | None:
         """Return the index of the frame/TracebackEntry where recursion originates if
         appropriate, None if no recursion occurred."""
-        cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {}
+        cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {}
         for i, entry in enumerate(self):
             # id for the code.raw is needed to work around
             # the strange metaprogramming in the decorator lib from pypi
             # which generates code objects that have hash/value equality
             # XXX needs a test
             key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
-            # print "checking for recursion at", key
             values = cache.setdefault(key, [])
+            # Since Python 3.13 f_locals is a proxy, freeze it.
+            loc = dict(entry.frame.f_locals)
             if values:
-                f = entry.frame
-                loc = f.f_locals
                 for otherloc in values:
                     if otherloc == loc:
                         return i
-            values.append(entry.frame.f_locals)
+            values.append(loc)
         return None
 
 
+def stringify_exception(
+    exc: BaseException, include_subexception_msg: bool = True
+) -> str:
+    try:
+        notes = getattr(exc, "__notes__", [])
+    except KeyError:
+        # Workaround for https://github.com/python/cpython/issues/98778 on
+        # Python <= 3.9, and some 3.10 and 3.11 patch versions.
+        HTTPError = getattr(sys.modules.get("urllib.error", None), "HTTPError", ())
+        if sys.version_info < (3, 12) and isinstance(exc, HTTPError):
+            notes = []
+        else:  # pragma: no cover
+            # exception not related to above bug, reraise
+            raise
+    if not include_subexception_msg and isinstance(exc, BaseExceptionGroup):
+        message = exc.message
+    else:
+        message = str(exc)
+
+    return "\n".join(
+        [
+            message,
+            *notes,
+        ]
+    )
+
+
 E = TypeVar("E", bound=BaseException, covariant=True)
 
 
 @final
-@attr.s(repr=False, init=False, auto_attribs=True)
+@dataclasses.dataclass
 class ExceptionInfo(Generic[E]):
     """Wraps sys.exc_info() objects and offers help for navigating the traceback."""
 
     _assert_start_repr: ClassVar = "AssertionError('assert "
 
-    _excinfo: Optional[Tuple[Type["E"], "E", TracebackType]]
+    _excinfo: tuple[type[E], E, TracebackType] | None
     _striptext: str
-    _traceback: Optional[Traceback]
+    _traceback: Traceback | None
 
     def __init__(
         self,
-        excinfo: Optional[Tuple[Type["E"], "E", TracebackType]],
+        excinfo: tuple[type[E], E, TracebackType] | None,
         striptext: str = "",
-        traceback: Optional[Traceback] = None,
+        traceback: Traceback | None = None,
         *,
         _ispytest: bool = False,
     ) -> None:
@@ -463,22 +514,42 @@ def __init__(
         self._traceback = traceback
 
     @classmethod
-    def from_exc_info(
+    def from_exception(
         cls,
-        exc_info: Tuple[Type[E], E, TracebackType],
-        exprinfo: Optional[str] = None,
-    ) -> "ExceptionInfo[E]":
-        """Return an ExceptionInfo for an existing exc_info tuple.
-
-        .. warning::
-
-            Experimental API
+        # Ignoring error: "Cannot use a covariant type variable as a parameter".
+        # This is OK to ignore because this class is (conceptually) readonly.
+        # See https://github.com/python/mypy/issues/7049.
+        exception: E,  # type: ignore[misc]
+        exprinfo: str | None = None,
+    ) -> ExceptionInfo[E]:
+        """Return an ExceptionInfo for an existing exception.
+
+        The exception must have a non-``None`` ``__traceback__`` attribute,
+        otherwise this function fails with an assertion error. This means that
+        the exception must have been raised, or added a traceback with the
+        :py:meth:`~BaseException.with_traceback()` method.
 
         :param exprinfo:
             A text string helping to determine if we should strip
             ``AssertionError`` from the output. Defaults to the exception
             message/``__str__()``.
+
+        .. versionadded:: 7.4
         """
+        assert exception.__traceback__, (
+            "Exceptions passed to ExcInfo.from_exception(...)"
+            " must have a non-None __traceback__."
+        )
+        exc_info = (type(exception), exception, exception.__traceback__)
+        return cls.from_exc_info(exc_info, exprinfo)
+
+    @classmethod
+    def from_exc_info(
+        cls,
+        exc_info: tuple[type[E], E, TracebackType],
+        exprinfo: str | None = None,
+    ) -> ExceptionInfo[E]:
+        """Like :func:`from_exception`, but using old-style exc_info tuple."""
         _striptext = ""
         if exprinfo is None and isinstance(exc_info[1], AssertionError):
             exprinfo = getattr(exc_info[1], "msg", None)
@@ -490,9 +561,7 @@ def from_exc_info(
         return cls(exc_info, _striptext, _ispytest=True)
 
     @classmethod
-    def from_current(
-        cls, exprinfo: Optional[str] = None
-    ) -> "ExceptionInfo[BaseException]":
+    def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]:
         """Return an ExceptionInfo matching the current traceback.
 
         .. warning::
@@ -512,52 +581,52 @@ def from_current(
         return ExceptionInfo.from_exc_info(exc_info, exprinfo)
 
     @classmethod
-    def for_later(cls) -> "ExceptionInfo[E]":
+    def for_later(cls) -> ExceptionInfo[E]:
         """Return an unfilled ExceptionInfo."""
         return cls(None, _ispytest=True)
 
-    def fill_unfilled(self, exc_info: Tuple[Type[E], E, TracebackType]) -> None:
+    def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None:
         """Fill an unfilled ExceptionInfo created with ``for_later()``."""
         assert self._excinfo is None, "ExceptionInfo was already filled"
         self._excinfo = exc_info
 
     @property
-    def type(self) -> Type[E]:
+    def type(self) -> type[E]:
         """The exception class."""
-        assert (
-            self._excinfo is not None
-        ), ".type can only be used after the context manager exits"
+        assert self._excinfo is not None, (
+            ".type can only be used after the context manager exits"
+        )
         return self._excinfo[0]
 
     @property
     def value(self) -> E:
         """The exception value."""
-        assert (
-            self._excinfo is not None
-        ), ".value can only be used after the context manager exits"
+        assert self._excinfo is not None, (
+            ".value can only be used after the context manager exits"
+        )
         return self._excinfo[1]
 
     @property
     def tb(self) -> TracebackType:
         """The exception raw traceback."""
-        assert (
-            self._excinfo is not None
-        ), ".tb can only be used after the context manager exits"
+        assert self._excinfo is not None, (
+            ".tb can only be used after the context manager exits"
+        )
         return self._excinfo[2]
 
     @property
     def typename(self) -> str:
         """The type name of the exception."""
-        assert (
-            self._excinfo is not None
-        ), ".typename can only be used after the context manager exits"
+        assert self._excinfo is not None, (
+            ".typename can only be used after the context manager exits"
+        )
         return self.type.__name__
 
     @property
     def traceback(self) -> Traceback:
         """The traceback."""
         if self._traceback is None:
-            self._traceback = Traceback(self.tb, excinfo=ref(self))
+            self._traceback = Traceback(self.tb)
         return self._traceback
 
     @traceback.setter
@@ -567,9 +636,7 @@ def traceback(self, value: Traceback) -> None:
     def __repr__(self) -> str:
         if self._excinfo is None:
             return "<ExceptionInfo for raises contextmanager>"
-        return "<{} {} tblen={}>".format(
-            self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback)
-        )
+        return f"<{self.__class__.__name__} {saferepr(self._excinfo[1])} tblen={len(self.traceback)}>"
 
     def exconly(self, tryshort: bool = False) -> str:
         """Return the exception as a string.
@@ -579,6 +646,23 @@ def exconly(self, tryshort: bool = False) -> str:
         representation is returned (so 'AssertionError: ' is removed from
         the beginning).
         """
+
+        def _get_single_subexc(
+            eg: BaseExceptionGroup[BaseException],
+        ) -> BaseException | None:
+            if len(eg.exceptions) != 1:
+                return None
+            if isinstance(e := eg.exceptions[0], BaseExceptionGroup):
+                return _get_single_subexc(e)
+            return e
+
+        if (
+            tryshort
+            and isinstance(self.value, BaseExceptionGroup)
+            and (subexc := _get_single_subexc(self.value)) is not None
+        ):
+            return f"{subexc!r} [single exception in {type(self.value).__name__}]"
+
         lines = format_exception_only(self.type, self.value)
         text = "".join(lines)
         text = text.rstrip()
@@ -587,31 +671,36 @@ def exconly(self, tryshort: bool = False) -> str:
                 text = text[len(self._striptext) :]
         return text
 
-    def errisinstance(
-        self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]]
-    ) -> bool:
+    def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool:
         """Return True if the exception is an instance of exc.
 
         Consider using ``isinstance(excinfo.value, exc)`` instead.
         """
         return isinstance(self.value, exc)
 
-    def _getreprcrash(self) -> "ReprFileLocation":
-        exconly = self.exconly(tryshort=True)
-        entry = self.traceback.getcrashentry()
-        path, lineno = entry.frame.code.raw.co_filename, entry.lineno
-        return ReprFileLocation(path, lineno + 1, exconly)
+    def _getreprcrash(self) -> ReprFileLocation | None:
+        # Find last non-hidden traceback entry that led to the exception of the
+        # traceback, or None if all hidden.
+        for i in range(-1, -len(self.traceback) - 1, -1):
+            entry = self.traceback[i]
+            if not entry.ishidden(self):
+                path, lineno = entry.frame.code.raw.co_filename, entry.lineno
+                exconly = self.exconly(tryshort=True)
+                return ReprFileLocation(path, lineno + 1, exconly)
+        return None
 
     def getrepr(
         self,
         showlocals: bool = False,
-        style: "_TracebackStyle" = "long",
+        style: TracebackStyle = "long",
         abspath: bool = False,
-        tbfilter: bool = True,
+        tbfilter: bool
+        | Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True,
         funcargs: bool = False,
         truncate_locals: bool = True,
+        truncate_args: bool = True,
         chain: bool = True,
-    ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
+    ) -> ReprExceptionInfo | ExceptionChainRepr:
         """Return str()able representation of this exception info.
 
         :param bool showlocals:
@@ -619,14 +708,20 @@ def getrepr(
             Ignored if ``style=="native"``.
 
         :param str style:
-            long|short|no|native|value traceback style.
+            long|short|line|no|native|value traceback style.
 
         :param bool abspath:
             If paths should be changed to absolute or left unchanged.
 
-        :param bool tbfilter:
-            Hide entries that contain a local variable ``__tracebackhide__==True``.
-            Ignored if ``style=="native"``.
+        :param tbfilter:
+            A filter for traceback entries.
+
+            * If false, don't hide any entries.
+            * If true, hide internal entries and entries that contain a local
+              variable ``__tracebackhide__ = True``.
+            * If a callable, delegates the filtering to the callable.
+
+            Ignored if ``style`` is ``"native"``.
 
         :param bool funcargs:
             Show fixtures ("funcargs" for legacy purposes) per traceback entry.
@@ -634,6 +729,9 @@ def getrepr(
         :param bool truncate_locals:
             With ``showlocals==True``, make sure locals can be safely represented as strings.
 
+        :param bool truncate_args:
+            With ``showargs==True``, make sure args can be safely represented as strings.
+
         :param bool chain:
             If chained exceptions in Python 3 should be shown.
 
@@ -643,12 +741,14 @@ def getrepr(
         """
         if style == "native":
             return ReprExceptionInfo(
-                ReprTracebackNative(
+                reprtraceback=ReprTracebackNative(
                     traceback.format_exception(
-                        self.type, self.value, self.traceback[0]._rawentry
+                        self.type,
+                        self.value,
+                        self.traceback[0]._rawentry if self.traceback else None,
                     )
                 ),
-                self._getreprcrash(),
+                reprcrash=self._getreprcrash(),
             )
 
         fmt = FormattedExcinfo(
@@ -658,26 +758,100 @@ def getrepr(
             tbfilter=tbfilter,
             funcargs=funcargs,
             truncate_locals=truncate_locals,
+            truncate_args=truncate_args,
             chain=chain,
         )
         return fmt.repr_excinfo(self)
 
-    def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]":
+    def match(self, regexp: str | re.Pattern[str]) -> Literal[True]:
         """Check whether the regular expression `regexp` matches the string
         representation of the exception using :func:`python:re.search`.
 
         If it matches `True` is returned, otherwise an `AssertionError` is raised.
         """
         __tracebackhide__ = True
-        msg = "Regex pattern {!r} does not match {!r}."
-        if regexp == str(self.value):
-            msg += " Did you mean to `re.escape()` the regex?"
-        assert re.search(regexp, str(self.value)), msg.format(regexp, str(self.value))
+        value = stringify_exception(self.value)
+        msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
+        if regexp == value:
+            msg += "\n Did you mean to `re.escape()` the regex?"
+        assert re.search(regexp, value), msg
         # Return True to allow for "assert excinfo.match()".
         return True
 
+    def _group_contains(
+        self,
+        exc_group: BaseExceptionGroup[BaseException],
+        expected_exception: EXCEPTION_OR_MORE,
+        match: str | re.Pattern[str] | None,
+        target_depth: int | None = None,
+        current_depth: int = 1,
+    ) -> bool:
+        """Return `True` if a `BaseExceptionGroup` contains a matching exception."""
+        if (target_depth is not None) and (current_depth > target_depth):
+            # already descended past the target depth
+            return False
+        for exc in exc_group.exceptions:
+            if isinstance(exc, BaseExceptionGroup):
+                if self._group_contains(
+                    exc, expected_exception, match, target_depth, current_depth + 1
+                ):
+                    return True
+            if (target_depth is not None) and (current_depth != target_depth):
+                # not at the target depth, no match
+                continue
+            if not isinstance(exc, expected_exception):
+                continue
+            if match is not None:
+                value = stringify_exception(exc)
+                if not re.search(match, value):
+                    continue
+            return True
+        return False
+
+    def group_contains(
+        self,
+        expected_exception: EXCEPTION_OR_MORE,
+        *,
+        match: str | re.Pattern[str] | None = None,
+        depth: int | None = None,
+    ) -> bool:
+        """Check whether a captured exception group contains a matching exception.
+
+        :param Type[BaseException] | Tuple[Type[BaseException]] expected_exception:
+            The expected exception type, or a tuple if one of multiple possible
+            exception types are expected.
+
+        :param str | re.Pattern[str] | None match:
+            If specified, a string containing a regular expression,
+            or a regular expression object, that is tested against the string
+            representation of the exception and its `PEP-678 <https://peps.python.org/pep-0678/>` `__notes__`
+            using :func:`re.search`.
+
+            To match a literal string that may contain :ref:`special characters
+            <re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
+
+        :param Optional[int] depth:
+            If `None`, will search for a matching exception at any nesting depth.
+            If >= 1, will only match an exception if it's at the specified depth (depth = 1 being
+            the exceptions contained within the topmost exception group).
+
+        .. versionadded:: 8.0
+
+        .. warning::
+           This helper makes it easy to check for the presence of specific exceptions,
+           but it is very bad for checking that the group does *not* contain
+           *any other exceptions*.
+           You should instead consider using :class:`pytest.RaisesGroup`
+
+        """
+        msg = "Captured exception is not an instance of `BaseExceptionGroup`"
+        assert isinstance(self.value, BaseExceptionGroup), msg
+        msg = "`depth` must be >= 1 if specified"
+        assert (depth is None) or (depth >= 1), msg
+        return self._group_contains(self.value, expected_exception, match, depth)
+
 
-@attr.s(auto_attribs=True)
+@dataclasses.dataclass
 class FormattedExcinfo:
     """Presenting information about failing Functions and Generators."""
 
@@ -686,17 +860,18 @@ class FormattedExcinfo:
     fail_marker: ClassVar = "E"
 
     showlocals: bool = False
-    style: "_TracebackStyle" = "long"
+    style: TracebackStyle = "long"
     abspath: bool = True
-    tbfilter: bool = True
+    tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True
     funcargs: bool = False
     truncate_locals: bool = True
+    truncate_args: bool = True
     chain: bool = True
-    astcache: Dict[Union[str, Path], ast.AST] = attr.ib(
-        factory=dict, init=False, repr=False
+    astcache: dict[str | Path, ast.AST] = dataclasses.field(
+        default_factory=dict, init=False, repr=False
     )
 
-    def _getindent(self, source: "Source") -> int:
+    def _getindent(self, source: Source) -> int:
         # Figure out indent for the given source.
         try:
             s = str(source.getstatement(len(source) - 1))
@@ -711,41 +886,70 @@ def _getindent(self, source: "Source") -> int:
                 return 0
         return 4 + (len(s) - len(s.lstrip()))
 
-    def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]:
+    def _getentrysource(self, entry: TracebackEntry) -> Source | None:
         source = entry.getsource(self.astcache)
         if source is not None:
             source = source.deindent()
         return source
 
-    def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]:
+    def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None:
         if self.funcargs:
             args = []
             for argname, argvalue in entry.frame.getargs(var=True):
-                args.append((argname, saferepr(argvalue)))
+                if self.truncate_args:
+                    str_repr = saferepr(argvalue)
+                else:
+                    str_repr = saferepr(argvalue, maxsize=None)
+                args.append((argname, str_repr))
             return ReprFuncArgs(args)
         return None
 
     def get_source(
         self,
-        source: Optional["Source"],
+        source: Source | None,
         line_index: int = -1,
-        excinfo: Optional[ExceptionInfo[BaseException]] = None,
+        excinfo: ExceptionInfo[BaseException] | None = None,
         short: bool = False,
-    ) -> List[str]:
+        end_line_index: int | None = None,
+        colno: int | None = None,
+        end_colno: int | None = None,
+    ) -> list[str]:
         """Return formatted and marked up source lines."""
         lines = []
-        if source is None or line_index >= len(source.lines):
+        if source is not None and line_index < 0:
+            line_index += len(source)
+        if source is None or line_index >= len(source.lines) or line_index < 0:
+            # `line_index` could still be outside `range(len(source.lines))` if
+            # we're processing AST with pathological position attributes.
             source = Source("???")
             line_index = 0
-        if line_index < 0:
-            line_index += len(source)
         space_prefix = "    "
         if short:
             lines.append(space_prefix + source.lines[line_index].strip())
+            lines.extend(
+                self.get_highlight_arrows_for_line(
+                    raw_line=source.raw_lines[line_index],
+                    line=source.lines[line_index].strip(),
+                    lineno=line_index,
+                    end_lineno=end_line_index,
+                    colno=colno,
+                    end_colno=end_colno,
+                )
+            )
         else:
             for line in source.lines[:line_index]:
                 lines.append(space_prefix + line)
             lines.append(self.flow_marker + "   " + source.lines[line_index])
+            lines.extend(
+                self.get_highlight_arrows_for_line(
+                    raw_line=source.raw_lines[line_index],
+                    line=source.lines[line_index],
+                    lineno=line_index,
+                    end_lineno=end_line_index,
+                    colno=colno,
+                    end_colno=end_colno,
+                )
+            )
             for line in source.lines[line_index + 1 :]:
                 lines.append(space_prefix + line)
         if excinfo is not None:
@@ -753,12 +957,49 @@ def get_source(
             lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
         return lines
 
+    def get_highlight_arrows_for_line(
+        self,
+        line: str,
+        raw_line: str,
+        lineno: int | None,
+        end_lineno: int | None,
+        colno: int | None,
+        end_colno: int | None,
+    ) -> list[str]:
+        """Return characters highlighting a source line.
+
+        Example with colno and end_colno pointing to the bar expression:
+                   "foo() + bar()"
+        returns    "        ^^^^^"
+        """
+        if lineno != end_lineno:
+            # Don't handle expressions that span multiple lines.
+            return []
+        if colno is None or end_colno is None:
+            # Can't do anything without column information.
+            return []
+
+        num_stripped_chars = len(raw_line) - len(line)
+
+        start_char_offset = _byte_offset_to_character_offset(raw_line, colno)
+        end_char_offset = _byte_offset_to_character_offset(raw_line, end_colno)
+        num_carets = end_char_offset - start_char_offset
+        # If the highlight would span the whole line, it is redundant, don't
+        # show it.
+        if num_carets >= len(line.strip()):
+            return []
+
+        highlights = "    "
+        highlights += " " * (start_char_offset - num_stripped_chars + 1)
+        highlights += "^" * num_carets
+        return [highlights]
+
     def get_exconly(
         self,
         excinfo: ExceptionInfo[BaseException],
         indent: int = 4,
         markall: bool = False,
-    ) -> List[str]:
+    ) -> list[str]:
         lines = []
         indentstr = " " * indent
         # Get the real exception information out.
@@ -770,7 +1011,7 @@ def get_exconly(
                 failindent = indentstr
         return lines
 
-    def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]:
+    def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None:
         if self.showlocals:
             lines = []
             keys = [loc for loc in locals if loc[0] != "@"]
@@ -798,26 +1039,42 @@ def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]:
 
     def repr_traceback_entry(
         self,
-        entry: TracebackEntry,
-        excinfo: Optional[ExceptionInfo[BaseException]] = None,
-    ) -> "ReprEntry":
-        lines: List[str] = []
-        style = entry._repr_style if entry._repr_style is not None else self.style
-        if style in ("short", "long"):
+        entry: TracebackEntry | None,
+        excinfo: ExceptionInfo[BaseException] | None = None,
+    ) -> ReprEntry:
+        lines: list[str] = []
+        style = (
+            entry._repr_style
+            if entry is not None and entry._repr_style is not None
+            else self.style
+        )
+        if style in ("short", "long") and entry is not None:
             source = self._getentrysource(entry)
             if source is None:
                 source = Source("???")
                 line_index = 0
+                end_line_index, colno, end_colno = None, None, None
             else:
-                line_index = entry.lineno - entry.getfirstlinesource()
+                line_index = entry.relline
+                end_line_index = entry.end_lineno_relative
+                colno = entry.colno
+                end_colno = entry.end_colno
             short = style == "short"
             reprargs = self.repr_args(entry) if not short else None
-            s = self.get_source(source, line_index, excinfo, short=short)
+            s = self.get_source(
+                source=source,
+                line_index=line_index,
+                excinfo=excinfo,
+                short=short,
+                end_line_index=end_line_index,
+                colno=colno,
+                end_colno=end_colno,
+            )
             lines.extend(s)
             if short:
-                message = "in %s" % (entry.name)
+                message = f"in {entry.name}"
             else:
-                message = excinfo and excinfo.typename or ""
+                message = (excinfo and excinfo.typename) or ""
             entry_path = entry.path
             path = self._makepath(entry_path)
             reprfileloc = ReprFileLocation(path, entry.lineno + 1, message)
@@ -832,7 +1089,7 @@ def repr_traceback_entry(
                 lines.extend(self.get_exconly(excinfo, indent=4))
             return ReprEntry(lines, None, None, None, style)
 
-    def _makepath(self, path: Union[Path, str]) -> str:
+    def _makepath(self, path: Path | str) -> str:
         if not self.abspath and isinstance(path, Path):
             try:
                 np = bestrelpath(Path.cwd(), path)
@@ -842,32 +1099,38 @@ def _makepath(self, path: Union[Path, str]) -> str:
                 return np
         return str(path)
 
-    def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback":
+    def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback:
         traceback = excinfo.traceback
-        if self.tbfilter:
-            traceback = traceback.filter()
+        if callable(self.tbfilter):
+            traceback = self.tbfilter(excinfo)
+        elif self.tbfilter:
+            traceback = traceback.filter(excinfo)
 
         if isinstance(excinfo.value, RecursionError):
             traceback, extraline = self._truncate_recursive_traceback(traceback)
         else:
             extraline = None
 
+        if not traceback:
+            if extraline is None:
+                extraline = "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames."
+            entries = [self.repr_traceback_entry(None, excinfo)]
+            return ReprTraceback(entries, extraline, style=self.style)
+
         last = traceback[-1]
-        entries = []
         if self.style == "value":
-            reprentry = self.repr_traceback_entry(last, excinfo)
-            entries.append(reprentry)
+            entries = [self.repr_traceback_entry(last, excinfo)]
             return ReprTraceback(entries, None, style=self.style)
 
-        for index, entry in enumerate(traceback):
-            einfo = (last == entry) and excinfo or None
-            reprentry = self.repr_traceback_entry(entry, einfo)
-            entries.append(reprentry)
+        entries = [
+            self.repr_traceback_entry(entry, excinfo if last == entry else None)
+            for entry in traceback
+        ]
         return ReprTraceback(entries, extraline, style=self.style)
 
     def _truncate_recursive_traceback(
         self, traceback: Traceback
-    ) -> Tuple[Traceback, Optional[str]]:
+    ) -> tuple[Traceback, str | None]:
         """Truncate the given recursive traceback trying to find the starting
         point of the recursion.
 
@@ -884,18 +1147,13 @@ def _truncate_recursive_traceback(
             recursionindex = traceback.recursionindex()
         except Exception as e:
             max_frames = 10
-            extraline: Optional[str] = (
+            extraline: str | None = (
                 "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n"
                 "  The following exception happened when comparing locals in the stack frame:\n"
-                "    {exc_type}: {exc_msg}\n"
-                "  Displaying first and last {max_frames} stack frames out of {total}."
-            ).format(
-                exc_type=type(e).__name__,
-                exc_msg=str(e),
-                max_frames=max_frames,
-                total=len(traceback),
+                f"    {type(e).__name__}: {e!s}\n"
+                f"  Displaying first and last {max_frames} stack frames out of {len(traceback)}."
             )
-            # Type ignored because adding two instaces of a List subtype
+            # Type ignored because adding two instances of a List subtype
             # currently incorrectly has type List instead of the subtype.
             traceback = traceback[:max_frames] + traceback[-max_frames:]  # type: ignore
         else:
@@ -907,23 +1165,32 @@ def _truncate_recursive_traceback(
 
         return traceback, extraline
 
-    def repr_excinfo(
-        self, excinfo: ExceptionInfo[BaseException]
-    ) -> "ExceptionChainRepr":
-        repr_chain: List[
-            Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]
-        ] = []
-        e: Optional[BaseException] = excinfo.value
-        excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo
+    def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr:
+        repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = []
+        e: BaseException | None = excinfo.value
+        excinfo_: ExceptionInfo[BaseException] | None = excinfo
         descr = None
-        seen: Set[int] = set()
+        seen: set[int] = set()
         while e is not None and id(e) not in seen:
             seen.add(id(e))
+
             if excinfo_:
-                reprtraceback = self.repr_traceback(excinfo_)
-                reprcrash: Optional[ReprFileLocation] = (
-                    excinfo_._getreprcrash() if self.style != "value" else None
-                )
+                # Fall back to native traceback as a temporary workaround until
+                # full support for exception groups added to ExceptionInfo.
+                # See https://github.com/pytest-dev/pytest/issues/9159
+                if isinstance(e, BaseExceptionGroup):
+                    reprtraceback: ReprTracebackNative | ReprTraceback = (
+                        ReprTracebackNative(
+                            traceback.format_exception(
+                                type(excinfo_.value),
+                                excinfo_.value,
+                                excinfo_.traceback[0]._rawentry,
+                            )
+                        )
+                    )
+                else:
+                    reprtraceback = self.repr_traceback(excinfo_)
+                reprcrash = excinfo_._getreprcrash()
             else:
                 # Fallback to native repr if the exception doesn't have a traceback:
                 # ExceptionInfo objects require a full traceback to work.
@@ -931,25 +1198,17 @@ def repr_excinfo(
                     traceback.format_exception(type(e), e, None)
                 )
                 reprcrash = None
-
             repr_chain += [(reprtraceback, reprcrash, descr)]
+
             if e.__cause__ is not None and self.chain:
                 e = e.__cause__
-                excinfo_ = (
-                    ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
-                    if e.__traceback__
-                    else None
-                )
+                excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None
                 descr = "The above exception was the direct cause of the following exception:"
             elif (
                 e.__context__ is not None and not e.__suppress_context__ and self.chain
             ):
                 e = e.__context__
-                excinfo_ = (
-                    ExceptionInfo.from_exc_info((type(e), e, e.__traceback__))
-                    if e.__traceback__
-                    else None
-                )
+                excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None
                 descr = "During handling of the above exception, another exception occurred:"
             else:
                 e = None
@@ -957,7 +1216,7 @@ def repr_excinfo(
         return ExceptionChainRepr(repr_chain)
 
 
-@attr.s(eq=False, auto_attribs=True)
+@dataclasses.dataclass(eq=False)
 class TerminalRepr:
     def __str__(self) -> str:
         # FYI this is called from pytest-xdist's serialization of exception
@@ -975,14 +1234,14 @@ def toterminal(self, tw: TerminalWriter) -> None:
 
 
 # This class is abstract -- only subclasses are instantiated.
-@attr.s(eq=False)
+@dataclasses.dataclass(eq=False)
 class ExceptionRepr(TerminalRepr):
     # Provided by subclasses.
-    reprcrash: Optional["ReprFileLocation"]
-    reprtraceback: "ReprTraceback"
-
-    def __attrs_post_init__(self) -> None:
-        self.sections: List[Tuple[str, str, str]] = []
+    reprtraceback: ReprTraceback
+    reprcrash: ReprFileLocation | None
+    sections: list[tuple[str, str, str]] = dataclasses.field(
+        init=False, default_factory=list
+    )
 
     def addsection(self, name: str, content: str, sep: str = "-") -> None:
         self.sections.append((name, content, sep))
@@ -993,16 +1252,21 @@ def toterminal(self, tw: TerminalWriter) -> None:
             tw.line(content)
 
 
-@attr.s(eq=False, auto_attribs=True)
+@dataclasses.dataclass(eq=False)
 class ExceptionChainRepr(ExceptionRepr):
-    chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]]
+    chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]]
 
-    def __attrs_post_init__(self) -> None:
-        super().__attrs_post_init__()
+    def __init__(
+        self,
+        chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]],
+    ) -> None:
         # reprcrash and reprtraceback of the outermost (the newest) exception
         # in the chain.
-        self.reprtraceback = self.chain[-1][0]
-        self.reprcrash = self.chain[-1][1]
+        super().__init__(
+            reprtraceback=chain[-1][0],
+            reprcrash=chain[-1][1],
+        )
+        self.chain = chain
 
     def toterminal(self, tw: TerminalWriter) -> None:
         for element in self.chain:
@@ -1013,21 +1277,21 @@ def toterminal(self, tw: TerminalWriter) -> None:
         super().toterminal(tw)
 
 
-@attr.s(eq=False, auto_attribs=True)
+@dataclasses.dataclass(eq=False)
 class ReprExceptionInfo(ExceptionRepr):
-    reprtraceback: "ReprTraceback"
-    reprcrash: "ReprFileLocation"
+    reprtraceback: ReprTraceback
+    reprcrash: ReprFileLocation | None
 
     def toterminal(self, tw: TerminalWriter) -> None:
         self.reprtraceback.toterminal(tw)
         super().toterminal(tw)
 
 
-@attr.s(eq=False, auto_attribs=True)
+@dataclasses.dataclass(eq=False)
 class ReprTraceback(TerminalRepr):
-    reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
-    extraline: Optional[str]
-    style: "_TracebackStyle"
+    reprentries: Sequence[ReprEntry | ReprEntryNative]
+    extraline: str | None
+    style: TracebackStyle
 
     entrysep: ClassVar = "_ "
 
@@ -1039,10 +1303,8 @@ def toterminal(self, tw: TerminalWriter) -> None:
             entry.toterminal(tw)
             if i < len(self.reprentries) - 1:
                 next_entry = self.reprentries[i + 1]
-                if (
-                    entry.style == "long"
-                    or entry.style == "short"
-                    and next_entry.style == "long"
+                if entry.style == "long" or (
+                    entry.style == "short" and next_entry.style == "long"
                 ):
                     tw.sep(self.entrysep)
 
@@ -1052,28 +1314,28 @@ def toterminal(self, tw: TerminalWriter) -> None:
 
 class ReprTracebackNative(ReprTraceback):
     def __init__(self, tblines: Sequence[str]) -> None:
-        self.style = "native"
         self.reprentries = [ReprEntryNative(tblines)]
         self.extraline = None
+        self.style = "native"
 
 
-@attr.s(eq=False, auto_attribs=True)
+@dataclasses.dataclass(eq=False)
 class ReprEntryNative(TerminalRepr):
     lines: Sequence[str]
 
-    style: ClassVar["_TracebackStyle"] = "native"
+    style: ClassVar[TracebackStyle] = "native"
 
     def toterminal(self, tw: TerminalWriter) -> None:
         tw.write("".join(self.lines))
 
 
-@attr.s(eq=False, auto_attribs=True)
+@dataclasses.dataclass(eq=False)
 class ReprEntry(TerminalRepr):
     lines: Sequence[str]
-    reprfuncargs: Optional["ReprFuncArgs"]
-    reprlocals: Optional["ReprLocals"]
-    reprfileloc: Optional["ReprFileLocation"]
-    style: "_TracebackStyle"
+    reprfuncargs: ReprFuncArgs | None
+    reprlocals: ReprLocals | None
+    reprfileloc: ReprFileLocation | None
+    style: TracebackStyle
 
     def _write_entry_lines(self, tw: TerminalWriter) -> None:
         """Write the source code portions of a list of traceback entries with syntax highlighting.
@@ -1088,18 +1350,26 @@ def _write_entry_lines(self, tw: TerminalWriter) -> None:
         the "E" prefix) using syntax highlighting, taking care to not highlighting the ">"
         character, as doing so might break line continuations.
         """
-
         if not self.lines:
             return
 
+        if self.style == "value":
+            # Using tw.write instead of tw.line for testing purposes due to TWMock implementation;
+            # lines written with TWMock.line and TWMock._write_source cannot be distinguished
+            # from each other, whereas lines written with TWMock.write are marked with TWMock.WRITE
+            for line in self.lines:
+                tw.write(line)
+                tw.write("\n")
+            return
+
         # separate indents and source lines that are not failures: we want to
         # highlight the code but not the indentation, which may contain markers
         # such as ">   assert 0"
         fail_marker = f"{FormattedExcinfo.fail_marker}   "
         indent_size = len(fail_marker)
-        indents: List[str] = []
-        source_lines: List[str] = []
-        failure_lines: List[str] = []
+        indents: list[str] = []
+        source_lines: list[str] = []
+        failure_lines: list[str] = []
         for index, line in enumerate(self.lines):
             is_failure_line = line.startswith(fail_marker)
             if is_failure_line:
@@ -1107,11 +1377,8 @@ def _write_entry_lines(self, tw: TerminalWriter) -> None:
                 failure_lines.extend(self.lines[index:])
                 break
             else:
-                if self.style == "value":
-                    source_lines.append(line)
-                else:
-                    indents.append(line[:indent_size])
-                    source_lines.append(line[indent_size:])
+                indents.append(line[:indent_size])
+                source_lines.append(line[indent_size:])
 
         tw._write_source(source_lines, indents)
 
@@ -1121,8 +1388,8 @@ def _write_entry_lines(self, tw: TerminalWriter) -> None:
 
     def toterminal(self, tw: TerminalWriter) -> None:
         if self.style == "short":
-            assert self.reprfileloc is not None
-            self.reprfileloc.toterminal(tw)
+            if self.reprfileloc:
+                self.reprfileloc.toterminal(tw)
             self._write_entry_lines(tw)
             if self.reprlocals:
                 self.reprlocals.toterminal(tw, indent=" " * 8)
@@ -1147,12 +1414,15 @@ def __str__(self) -> str:
         )
 
 
-@attr.s(eq=False, auto_attribs=True)
+@dataclasses.dataclass(eq=False)
 class ReprFileLocation(TerminalRepr):
-    path: str = attr.ib(converter=str)
+    path: str
     lineno: int
     message: str
 
+    def __post_init__(self) -> None:
+        self.path = str(self.path)
+
     def toterminal(self, tw: TerminalWriter) -> None:
         # Filename and lineno output for each entry, using an output format
         # that most editors understand.
@@ -1164,7 +1434,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
         tw.line(f":{self.lineno}: {msg}")
 
 
-@attr.s(eq=False, auto_attribs=True)
+@dataclasses.dataclass(eq=False)
 class ReprLocals(TerminalRepr):
     lines: Sequence[str]
 
@@ -1173,9 +1443,9 @@ def toterminal(self, tw: TerminalWriter, indent="") -> None:
             tw.line(indent + line)
 
 
-@attr.s(eq=False, auto_attribs=True)
+@dataclasses.dataclass(eq=False)
 class ReprFuncArgs(TerminalRepr):
-    args: Sequence[Tuple[str, object]]
+    args: Sequence[tuple[str, object]]
 
     def toterminal(self, tw: TerminalWriter) -> None:
         if self.args:
@@ -1196,7 +1466,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
             tw.line("")
 
 
-def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
+def getfslineno(obj: object) -> tuple[str | Path, int]:
     """Return source location (path, lineno) for the given object.
 
     If the source cannot be determined return ("", -1).
@@ -1208,7 +1478,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
     #       in 6ec13a2b9.  It ("place_as") appears to be something very custom.
     obj = get_real_func(obj)
     if hasattr(obj, "place_as"):
-        obj = obj.place_as  # type: ignore[attr-defined]
+        obj = obj.place_as
 
     try:
         code = Code.from_function(obj)
@@ -1218,7 +1488,7 @@ def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
         except TypeError:
             return "", -1
 
-        fspath = fn and absolutepath(fn) or ""
+        fspath = (fn and absolutepath(fn)) or ""
         lineno = -1
         if fspath:
             try:
@@ -1230,6 +1500,12 @@ def getfslineno(obj: object) -> Tuple[Union[str, Path], int]:
     return code.path, code.firstlineno
 
 
+def _byte_offset_to_character_offset(str, offset):
+    """Converts a byte based offset in a string to a code-point."""
+    as_utf8 = str.encode("utf-8")
+    return len(as_utf8[:offset].decode("utf-8", errors="replace"))
+
+
 # Relative paths that we use to filter traceback entries from appearing to the user;
 # see filter_traceback.
 # note: if we need to add more paths than what we have now we should probably use a list
diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py
index 6f54057c0a9..a8f7201a40f 100644
--- a/src/_pytest/_code/source.py
+++ b/src/_pytest/_code/source.py
@@ -1,17 +1,16 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import ast
+from bisect import bisect_right
+from collections.abc import Iterable
+from collections.abc import Iterator
 import inspect
 import textwrap
 import tokenize
 import types
-import warnings
-from bisect import bisect_right
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
 from typing import overload
-from typing import Tuple
-from typing import Union
+import warnings
 
 
 class Source:
@@ -22,13 +21,17 @@ class Source:
 
     def __init__(self, obj: object = None) -> None:
         if not obj:
-            self.lines: List[str] = []
+            self.lines: list[str] = []
+            self.raw_lines: list[str] = []
         elif isinstance(obj, Source):
             self.lines = obj.lines
+            self.raw_lines = obj.raw_lines
         elif isinstance(obj, (tuple, list)):
             self.lines = deindent(x.rstrip("\n") for x in obj)
+            self.raw_lines = list(x.rstrip("\n") for x in obj)
         elif isinstance(obj, str):
             self.lines = deindent(obj.split("\n"))
+            self.raw_lines = obj.split("\n")
         else:
             try:
                 rawcode = getrawcode(obj)
@@ -36,6 +39,7 @@ def __init__(self, obj: object = None) -> None:
             except TypeError:
                 src = inspect.getsource(obj)  # type: ignore[arg-type]
             self.lines = deindent(src.split("\n"))
+            self.raw_lines = src.split("\n")
 
     def __eq__(self, other: object) -> bool:
         if not isinstance(other, Source):
@@ -46,14 +50,12 @@ def __eq__(self, other: object) -> bool:
     __hash__ = None  # type: ignore
 
     @overload
-    def __getitem__(self, key: int) -> str:
-        ...
+    def __getitem__(self, key: int) -> str: ...
 
     @overload
-    def __getitem__(self, key: slice) -> "Source":
-        ...
+    def __getitem__(self, key: slice) -> Source: ...
 
-    def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]:
+    def __getitem__(self, key: int | slice) -> str | Source:
         if isinstance(key, int):
             return self.lines[key]
         else:
@@ -61,6 +63,7 @@ def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]:
                 raise IndexError("cannot slice a Source with a step")
             newsource = Source()
             newsource.lines = self.lines[key.start : key.stop]
+            newsource.raw_lines = self.raw_lines[key.start : key.stop]
             return newsource
 
     def __iter__(self) -> Iterator[str]:
@@ -69,7 +72,7 @@ def __iter__(self) -> Iterator[str]:
     def __len__(self) -> int:
         return len(self.lines)
 
-    def strip(self) -> "Source":
+    def strip(self) -> Source:
         """Return new Source object with trailing and leading blank lines removed."""
         start, end = 0, len(self)
         while start < end and not self.lines[start].strip():
@@ -77,23 +80,25 @@ def strip(self) -> "Source":
         while end > start and not self.lines[end - 1].strip():
             end -= 1
         source = Source()
+        source.raw_lines = self.raw_lines
         source.lines[:] = self.lines[start:end]
         return source
 
-    def indent(self, indent: str = " " * 4) -> "Source":
+    def indent(self, indent: str = " " * 4) -> Source:
         """Return a copy of the source object with all lines indented by the
         given indent-string."""
         newsource = Source()
+        newsource.raw_lines = self.raw_lines
         newsource.lines = [(indent + line) for line in self.lines]
         return newsource
 
-    def getstatement(self, lineno: int) -> "Source":
+    def getstatement(self, lineno: int) -> Source:
         """Return Source statement which contains the given linenumber
         (counted from 0)."""
         start, end = self.getstatementrange(lineno)
         return self[start:end]
 
-    def getstatementrange(self, lineno: int) -> Tuple[int, int]:
+    def getstatementrange(self, lineno: int) -> tuple[int, int]:
         """Return (start, end) tuple which spans the minimal statement region
         which containing the given lineno."""
         if not (0 <= lineno < len(self)):
@@ -101,10 +106,11 @@ def getstatementrange(self, lineno: int) -> Tuple[int, int]:
         ast, start, end = getstatementrange_ast(lineno, self)
         return start, end
 
-    def deindent(self) -> "Source":
+    def deindent(self) -> Source:
         """Return a new Source object deindented."""
         newsource = Source()
         newsource.lines[:] = deindent(self.lines)
+        newsource.raw_lines = self.raw_lines
         return newsource
 
     def __str__(self) -> str:
@@ -116,13 +122,14 @@ def __str__(self) -> str:
 #
 
 
-def findsource(obj) -> Tuple[Optional[Source], int]:
+def findsource(obj) -> tuple[Source | None, int]:
     try:
         sourcelines, lineno = inspect.findsource(obj)
     except Exception:
         return None, -1
     source = Source()
     source.lines = [line.rstrip() for line in sourcelines]
+    source.raw_lines = sourcelines
     return source, lineno
 
 
@@ -139,19 +146,23 @@ def getrawcode(obj: object, trycall: bool = True) -> types.CodeType:
     raise TypeError(f"could not get code object for {obj!r}")
 
 
-def deindent(lines: Iterable[str]) -> List[str]:
+def deindent(lines: Iterable[str]) -> list[str]:
     return textwrap.dedent("\n".join(lines)).splitlines()
 
 
-def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]:
+def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]:
     # Flatten all statements and except handlers into one lineno-list.
     # AST's line numbers start indexing at 1.
-    values: List[int] = []
+    values: list[int] = []
     for x in ast.walk(node):
         if isinstance(x, (ast.stmt, ast.ExceptHandler)):
+            # The lineno points to the class/def, so need to include the decorators.
+            if isinstance(x, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
+                for d in x.decorator_list:
+                    values.append(d.lineno - 1)
             values.append(x.lineno - 1)
             for name in ("finalbody", "orelse"):
-                val: Optional[List[ast.stmt]] = getattr(x, name, None)
+                val: list[ast.stmt] | None = getattr(x, name, None)
                 if val:
                     # Treat the finally/orelse part as its own statement.
                     values.append(val[0].lineno - 1 - 1)
@@ -169,8 +180,8 @@ def getstatementrange_ast(
     lineno: int,
     source: Source,
     assertion: bool = False,
-    astnode: Optional[ast.AST] = None,
-) -> Tuple[ast.AST, int, int]:
+    astnode: ast.AST | None = None,
+) -> tuple[ast.AST, int, int]:
     if astnode is None:
         content = str(source)
         # See #4260:
@@ -192,7 +203,9 @@ def getstatementrange_ast(
         # by using the BlockFinder helper used which inspect.getsource() uses itself.
         block_finder = inspect.BlockFinder()
         # If we start with an indented line, put blockfinder to "started" mode.
-        block_finder.started = source.lines[start][0].isspace()
+        block_finder.started = (
+            bool(source.lines[start]) and source.lines[start][0].isspace()
+        )
         it = ((x + "\n") for x in source.lines[start:end])
         try:
             for tok in tokenize.generate_tokens(lambda: next(it)):
diff --git a/src/_pytest/_io/__init__.py b/src/_pytest/_io/__init__.py
index db001e918cb..b0155b18b60 100644
--- a/src/_pytest/_io/__init__.py
+++ b/src/_pytest/_io/__init__.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from .terminalwriter import get_terminal_width
 from .terminalwriter import TerminalWriter
 
diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py
new file mode 100644
index 00000000000..28f06909206
--- /dev/null
+++ b/src/_pytest/_io/pprint.py
@@ -0,0 +1,673 @@
+# mypy: allow-untyped-defs
+# This module was imported from the cpython standard library
+# (https://github.com/python/cpython/) at commit
+# c5140945c723ae6c4b7ee81ff720ac8ea4b52cfd (python3.12).
+#
+#
+#  Original Author:      Fred L. Drake, Jr.
+#                        fdrake@acm.org
+#
+#  This is a simple little module I wrote to make life easier.  I didn't
+#  see anything quite like it in the library, though I may have overlooked
+#  something.  I wrote this when I was trying to read some heavily nested
+#  tuples with fairly non-descriptive content.  This is modeled very much
+#  after Lisp/Scheme - style pretty-printing of lists.  If you find it
+#  useful, thank small children who sleep at night.
+from __future__ import annotations
+
+import collections as _collections
+from collections.abc import Callable
+from collections.abc import Iterator
+import dataclasses as _dataclasses
+from io import StringIO as _StringIO
+import re
+import types as _types
+from typing import Any
+from typing import IO
+
+
+class _safe_key:
+    """Helper function for key functions when sorting unorderable objects.
+
+    The wrapped-object will fallback to a Py2.x style comparison for
+    unorderable types (sorting first comparing the type name and then by
+    the obj ids).  Does not work recursively, so dict.items() must have
+    _safe_key applied to both the key and the value.
+
+    """
+
+    __slots__ = ["obj"]
+
+    def __init__(self, obj):
+        self.obj = obj
+
+    def __lt__(self, other):
+        try:
+            return self.obj < other.obj
+        except TypeError:
+            return (str(type(self.obj)), id(self.obj)) < (
+                str(type(other.obj)),
+                id(other.obj),
+            )
+
+
+def _safe_tuple(t):
+    """Helper function for comparing 2-tuples"""
+    return _safe_key(t[0]), _safe_key(t[1])
+
+
+class PrettyPrinter:
+    def __init__(
+        self,
+        indent: int = 4,
+        width: int = 80,
+        depth: int | None = None,
+    ) -> None:
+        """Handle pretty printing operations onto a stream using a set of
+        configured parameters.
+
+        indent
+            Number of spaces to indent for each level of nesting.
+
+        width
+            Attempted maximum number of columns in the output.
+
+        depth
+            The maximum depth to print out nested structures.
+
+        """
+        if indent < 0:
+            raise ValueError("indent must be >= 0")
+        if depth is not None and depth <= 0:
+            raise ValueError("depth must be > 0")
+        if not width:
+            raise ValueError("width must be != 0")
+        self._depth = depth
+        self._indent_per_level = indent
+        self._width = width
+
+    def pformat(self, object: Any) -> str:
+        sio = _StringIO()
+        self._format(object, sio, 0, 0, set(), 0)
+        return sio.getvalue()
+
+    def _format(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        objid = id(object)
+        if objid in context:
+            stream.write(_recursion(object))
+            return
+
+        p = self._dispatch.get(type(object).__repr__, None)
+        if p is not None:
+            context.add(objid)
+            p(self, object, stream, indent, allowance, context, level + 1)
+            context.remove(objid)
+        elif (
+            _dataclasses.is_dataclass(object)
+            and not isinstance(object, type)
+            and object.__dataclass_params__.repr  # type:ignore[attr-defined]
+            and
+            # Check dataclass has generated repr method.
+            hasattr(object.__repr__, "__wrapped__")
+            and "__create_fn__" in object.__repr__.__wrapped__.__qualname__
+        ):
+            context.add(objid)
+            self._pprint_dataclass(
+                object, stream, indent, allowance, context, level + 1
+            )
+            context.remove(objid)
+        else:
+            stream.write(self._repr(object, context, level))
+
+    def _pprint_dataclass(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        cls_name = object.__class__.__name__
+        items = [
+            (f.name, getattr(object, f.name))
+            for f in _dataclasses.fields(object)
+            if f.repr
+        ]
+        stream.write(cls_name + "(")
+        self._format_namespace_items(items, stream, indent, allowance, context, level)
+        stream.write(")")
+
+    _dispatch: dict[
+        Callable[..., str],
+        Callable[[PrettyPrinter, Any, IO[str], int, int, set[int], int], None],
+    ] = {}
+
+    def _pprint_dict(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        write = stream.write
+        write("{")
+        items = sorted(object.items(), key=_safe_tuple)
+        self._format_dict_items(items, stream, indent, allowance, context, level)
+        write("}")
+
+    _dispatch[dict.__repr__] = _pprint_dict
+
+    def _pprint_ordered_dict(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        if not len(object):
+            stream.write(repr(object))
+            return
+        cls = object.__class__
+        stream.write(cls.__name__ + "(")
+        self._pprint_dict(object, stream, indent, allowance, context, level)
+        stream.write(")")
+
+    _dispatch[_collections.OrderedDict.__repr__] = _pprint_ordered_dict
+
+    def _pprint_list(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        stream.write("[")
+        self._format_items(object, stream, indent, allowance, context, level)
+        stream.write("]")
+
+    _dispatch[list.__repr__] = _pprint_list
+
+    def _pprint_tuple(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        stream.write("(")
+        self._format_items(object, stream, indent, allowance, context, level)
+        stream.write(")")
+
+    _dispatch[tuple.__repr__] = _pprint_tuple
+
+    def _pprint_set(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        if not len(object):
+            stream.write(repr(object))
+            return
+        typ = object.__class__
+        if typ is set:
+            stream.write("{")
+            endchar = "}"
+        else:
+            stream.write(typ.__name__ + "({")
+            endchar = "})"
+        object = sorted(object, key=_safe_key)
+        self._format_items(object, stream, indent, allowance, context, level)
+        stream.write(endchar)
+
+    _dispatch[set.__repr__] = _pprint_set
+    _dispatch[frozenset.__repr__] = _pprint_set
+
+    def _pprint_str(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        write = stream.write
+        if not len(object):
+            write(repr(object))
+            return
+        chunks = []
+        lines = object.splitlines(True)
+        if level == 1:
+            indent += 1
+            allowance += 1
+        max_width1 = max_width = self._width - indent
+        for i, line in enumerate(lines):
+            rep = repr(line)
+            if i == len(lines) - 1:
+                max_width1 -= allowance
+            if len(rep) <= max_width1:
+                chunks.append(rep)
+            else:
+                # A list of alternating (non-space, space) strings
+                parts = re.findall(r"\S*\s*", line)
+                assert parts
+                assert not parts[-1]
+                parts.pop()  # drop empty last part
+                max_width2 = max_width
+                current = ""
+                for j, part in enumerate(parts):
+                    candidate = current + part
+                    if j == len(parts) - 1 and i == len(lines) - 1:
+                        max_width2 -= allowance
+                    if len(repr(candidate)) > max_width2:
+                        if current:
+                            chunks.append(repr(current))
+                        current = part
+                    else:
+                        current = candidate
+                if current:
+                    chunks.append(repr(current))
+        if len(chunks) == 1:
+            write(rep)
+            return
+        if level == 1:
+            write("(")
+        for i, rep in enumerate(chunks):
+            if i > 0:
+                write("\n" + " " * indent)
+            write(rep)
+        if level == 1:
+            write(")")
+
+    _dispatch[str.__repr__] = _pprint_str
+
+    def _pprint_bytes(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        write = stream.write
+        if len(object) <= 4:
+            write(repr(object))
+            return
+        parens = level == 1
+        if parens:
+            indent += 1
+            allowance += 1
+            write("(")
+        delim = ""
+        for rep in _wrap_bytes_repr(object, self._width - indent, allowance):
+            write(delim)
+            write(rep)
+            if not delim:
+                delim = "\n" + " " * indent
+        if parens:
+            write(")")
+
+    _dispatch[bytes.__repr__] = _pprint_bytes
+
+    def _pprint_bytearray(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        write = stream.write
+        write("bytearray(")
+        self._pprint_bytes(
+            bytes(object), stream, indent + 10, allowance + 1, context, level + 1
+        )
+        write(")")
+
+    _dispatch[bytearray.__repr__] = _pprint_bytearray
+
+    def _pprint_mappingproxy(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        stream.write("mappingproxy(")
+        self._format(object.copy(), stream, indent, allowance, context, level)
+        stream.write(")")
+
+    _dispatch[_types.MappingProxyType.__repr__] = _pprint_mappingproxy
+
+    def _pprint_simplenamespace(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        if type(object) is _types.SimpleNamespace:
+            # The SimpleNamespace repr is "namespace" instead of the class
+            # name, so we do the same here. For subclasses; use the class name.
+            cls_name = "namespace"
+        else:
+            cls_name = object.__class__.__name__
+        items = object.__dict__.items()
+        stream.write(cls_name + "(")
+        self._format_namespace_items(items, stream, indent, allowance, context, level)
+        stream.write(")")
+
+    _dispatch[_types.SimpleNamespace.__repr__] = _pprint_simplenamespace
+
+    def _format_dict_items(
+        self,
+        items: list[tuple[Any, Any]],
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        if not items:
+            return
+
+        write = stream.write
+        item_indent = indent + self._indent_per_level
+        delimnl = "\n" + " " * item_indent
+        for key, ent in items:
+            write(delimnl)
+            write(self._repr(key, context, level))
+            write(": ")
+            self._format(ent, stream, item_indent, 1, context, level)
+            write(",")
+
+        write("\n" + " " * indent)
+
+    def _format_namespace_items(
+        self,
+        items: list[tuple[Any, Any]],
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        if not items:
+            return
+
+        write = stream.write
+        item_indent = indent + self._indent_per_level
+        delimnl = "\n" + " " * item_indent
+        for key, ent in items:
+            write(delimnl)
+            write(key)
+            write("=")
+            if id(ent) in context:
+                # Special-case representation of recursion to match standard
+                # recursive dataclass repr.
+                write("...")
+            else:
+                self._format(
+                    ent,
+                    stream,
+                    item_indent + len(key) + 1,
+                    1,
+                    context,
+                    level,
+                )
+
+            write(",")
+
+        write("\n" + " " * indent)
+
+    def _format_items(
+        self,
+        items: list[Any],
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        if not items:
+            return
+
+        write = stream.write
+        item_indent = indent + self._indent_per_level
+        delimnl = "\n" + " " * item_indent
+
+        for item in items:
+            write(delimnl)
+            self._format(item, stream, item_indent, 1, context, level)
+            write(",")
+
+        write("\n" + " " * indent)
+
+    def _repr(self, object: Any, context: set[int], level: int) -> str:
+        return self._safe_repr(object, context.copy(), self._depth, level)
+
+    def _pprint_default_dict(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        rdf = self._repr(object.default_factory, context, level)
+        stream.write(f"{object.__class__.__name__}({rdf}, ")
+        self._pprint_dict(object, stream, indent, allowance, context, level)
+        stream.write(")")
+
+    _dispatch[_collections.defaultdict.__repr__] = _pprint_default_dict
+
+    def _pprint_counter(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        stream.write(object.__class__.__name__ + "(")
+
+        if object:
+            stream.write("{")
+            items = object.most_common()
+            self._format_dict_items(items, stream, indent, allowance, context, level)
+            stream.write("}")
+
+        stream.write(")")
+
+    _dispatch[_collections.Counter.__repr__] = _pprint_counter
+
+    def _pprint_chain_map(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])):
+            stream.write(repr(object))
+            return
+
+        stream.write(object.__class__.__name__ + "(")
+        self._format_items(object.maps, stream, indent, allowance, context, level)
+        stream.write(")")
+
+    _dispatch[_collections.ChainMap.__repr__] = _pprint_chain_map
+
+    def _pprint_deque(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        stream.write(object.__class__.__name__ + "(")
+        if object.maxlen is not None:
+            stream.write(f"maxlen={object.maxlen}, ")
+        stream.write("[")
+
+        self._format_items(object, stream, indent, allowance + 1, context, level)
+        stream.write("])")
+
+    _dispatch[_collections.deque.__repr__] = _pprint_deque
+
+    def _pprint_user_dict(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        self._format(object.data, stream, indent, allowance, context, level - 1)
+
+    _dispatch[_collections.UserDict.__repr__] = _pprint_user_dict
+
+    def _pprint_user_list(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        self._format(object.data, stream, indent, allowance, context, level - 1)
+
+    _dispatch[_collections.UserList.__repr__] = _pprint_user_list
+
+    def _pprint_user_string(
+        self,
+        object: Any,
+        stream: IO[str],
+        indent: int,
+        allowance: int,
+        context: set[int],
+        level: int,
+    ) -> None:
+        self._format(object.data, stream, indent, allowance, context, level - 1)
+
+    _dispatch[_collections.UserString.__repr__] = _pprint_user_string
+
+    def _safe_repr(
+        self, object: Any, context: set[int], maxlevels: int | None, level: int
+    ) -> str:
+        typ = type(object)
+        if typ in _builtin_scalars:
+            return repr(object)
+
+        r = getattr(typ, "__repr__", None)
+
+        if issubclass(typ, dict) and r is dict.__repr__:
+            if not object:
+                return "{}"
+            objid = id(object)
+            if maxlevels and level >= maxlevels:
+                return "{...}"
+            if objid in context:
+                return _recursion(object)
+            context.add(objid)
+            components: list[str] = []
+            append = components.append
+            level += 1
+            for k, v in sorted(object.items(), key=_safe_tuple):
+                krepr = self._safe_repr(k, context, maxlevels, level)
+                vrepr = self._safe_repr(v, context, maxlevels, level)
+                append(f"{krepr}: {vrepr}")
+            context.remove(objid)
+            return "{{{}}}".format(", ".join(components))
+
+        if (issubclass(typ, list) and r is list.__repr__) or (
+            issubclass(typ, tuple) and r is tuple.__repr__
+        ):
+            if issubclass(typ, list):
+                if not object:
+                    return "[]"
+                format = "[%s]"
+            elif len(object) == 1:
+                format = "(%s,)"
+            else:
+                if not object:
+                    return "()"
+                format = "(%s)"
+            objid = id(object)
+            if maxlevels and level >= maxlevels:
+                return format % "..."
+            if objid in context:
+                return _recursion(object)
+            context.add(objid)
+            components = []
+            append = components.append
+            level += 1
+            for o in object:
+                orepr = self._safe_repr(o, context, maxlevels, level)
+                append(orepr)
+            context.remove(objid)
+            return format % ", ".join(components)
+
+        return repr(object)
+
+
+_builtin_scalars = frozenset(
+    {str, bytes, bytearray, float, complex, bool, type(None), int}
+)
+
+
+def _recursion(object: Any) -> str:
+    return f"<Recursion on {type(object).__name__} with id={id(object)}>"
+
+
+def _wrap_bytes_repr(object: Any, width: int, allowance: int) -> Iterator[str]:
+    current = b""
+    last = len(object) // 4 * 4
+    for i in range(0, len(object), 4):
+        part = object[i : i + 4]
+        candidate = current + part
+        if i == last:
+            width -= allowance
+        if len(repr(candidate)) > width:
+            if current:
+                yield repr(current)
+            current = part
+        else:
+            current = candidate
+    if current:
+        yield repr(current)
diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py
index e7ff5cab203..cee70e332f9 100644
--- a/src/_pytest/_io/saferepr.py
+++ b/src/_pytest/_io/saferepr.py
@@ -1,9 +1,7 @@
+from __future__ import annotations
+
 import pprint
 import reprlib
-from typing import Any
-from typing import Dict
-from typing import IO
-from typing import Optional
 
 
 def _try_repr_or_str(obj: object) -> str:
@@ -20,10 +18,10 @@ def _format_repr_exception(exc: BaseException, obj: object) -> str:
         exc_info = _try_repr_or_str(exc)
     except (KeyboardInterrupt, SystemExit):
         raise
-    except BaseException as exc:
-        exc_info = f"unpresentable exception ({_try_repr_or_str(exc)})"
-    return "<[{} raised in repr()] {} object at 0x{:x}>".format(
-        exc_info, type(obj).__name__, id(obj)
+    except BaseException as inner_exc:
+        exc_info = f"unpresentable exception ({_try_repr_or_str(inner_exc)})"
+    return (
+        f"<[{exc_info} raised in repr()] {type(obj).__name__} object at 0x{id(obj):x}>"
     )
 
 
@@ -41,7 +39,7 @@ class SafeRepr(reprlib.Repr):
     information on exceptions raised during the call.
     """
 
-    def __init__(self, maxsize: Optional[int]) -> None:
+    def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None:
         """
         :param maxsize:
             If not None, will truncate the resulting repr to that specific size, using ellipsis
@@ -54,10 +52,14 @@ def __init__(self, maxsize: Optional[int]) -> None:
         # truncation.
         self.maxstring = maxsize if maxsize is not None else 1_000_000_000
         self.maxsize = maxsize
+        self.use_ascii = use_ascii
 
     def repr(self, x: object) -> str:
         try:
-            s = super().repr(x)
+            if self.use_ascii:
+                s = ascii(x)
+            else:
+                s = super().repr(x)
         except (KeyboardInterrupt, SystemExit):
             raise
         except BaseException as exc:
@@ -94,7 +96,9 @@ def safeformat(obj: object) -> str:
 DEFAULT_REPR_MAX_SIZE = 240
 
 
-def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str:
+def saferepr(
+    obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False
+) -> str:
     """Return a size-limited safe repr-string for the given object.
 
     Failing __repr__ functions of user instances will be represented
@@ -104,50 +108,23 @@ def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str
     This function is a wrapper around the Repr/reprlib functionality of the
     stdlib.
     """
-    return SafeRepr(maxsize).repr(obj)
-
-
-class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
-    """PrettyPrinter that always dispatches (regardless of width)."""
-
-    def _format(
-        self,
-        object: object,
-        stream: IO[str],
-        indent: int,
-        allowance: int,
-        context: Dict[int, Any],
-        level: int,
-    ) -> None:
-        # Type ignored because _dispatch is private.
-        p = self._dispatch.get(type(object).__repr__, None)  # type: ignore[attr-defined]
-
-        objid = id(object)
-        if objid in context or p is None:
-            # Type ignored because _format is private.
-            super()._format(  # type: ignore[misc]
-                object,
-                stream,
-                indent,
-                allowance,
-                context,
-                level,
-            )
-            return
-
-        context[objid] = 1
-        p(self, object, stream, indent, allowance, context, level + 1)
-        del context[objid]
-
-
-def _pformat_dispatch(
-    object: object,
-    indent: int = 1,
-    width: int = 80,
-    depth: Optional[int] = None,
-    *,
-    compact: bool = False,
-) -> str:
-    return AlwaysDispatchingPrettyPrinter(
-        indent=indent, width=width, depth=depth, compact=compact
-    ).pformat(object)
+    return SafeRepr(maxsize, use_ascii).repr(obj)
+
+
+def saferepr_unlimited(obj: object, use_ascii: bool = True) -> str:
+    """Return an unlimited-size safe repr-string for the given object.
+
+    As with saferepr, failing __repr__ functions of user instances
+    will be represented with a short exception info.
+
+    This function is a wrapper around simple repr.
+
+    Note: a cleaner solution would be to alter ``saferepr``this way
+    when maxsize=None, but that might affect some other code.
+    """
+    try:
+        if use_ascii:
+            return ascii(obj)
+        return repr(obj)
+    except Exception as exc:
+        return _format_repr_exception(exc, obj)
diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py
index 379035d858c..fd808f8b3b7 100644
--- a/src/_pytest/_io/terminalwriter.py
+++ b/src/_pytest/_io/terminalwriter.py
@@ -1,13 +1,23 @@
 """Helper functions for writing to terminals and files."""
+
+from __future__ import annotations
+
+from collections.abc import Sequence
 import os
 import shutil
 import sys
-from typing import Optional
-from typing import Sequence
+from typing import final
+from typing import Literal
 from typing import TextIO
 
+import pygments
+from pygments.formatters.terminal import TerminalFormatter
+from pygments.lexer import Lexer
+from pygments.lexers.diff import DiffLexer
+from pygments.lexers.python import PythonLexer
+
+from ..compat import assert_never
 from .wcwidth import wcswidth
-from _pytest.compat import final
 
 
 # This code was initially copied from py 1.8.1, file _io/terminalwriter.py.
@@ -28,9 +38,9 @@ def should_do_markup(file: TextIO) -> bool:
         return True
     if os.environ.get("PY_COLORS") == "0":
         return False
-    if "NO_COLOR" in os.environ:
+    if os.environ.get("NO_COLOR"):
         return False
-    if "FORCE_COLOR" in os.environ:
+    if os.environ.get("FORCE_COLOR"):
         return True
     return (
         hasattr(file, "isatty") and file.isatty() and os.environ.get("TERM") != "dumb"
@@ -62,7 +72,7 @@ class TerminalWriter:
         invert=7,
     )
 
-    def __init__(self, file: Optional[TextIO] = None) -> None:
+    def __init__(self, file: TextIO | None = None) -> None:
         if file is None:
             file = sys.stdout
         if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32":
@@ -76,7 +86,7 @@ def __init__(self, file: Optional[TextIO] = None) -> None:
         self._file = file
         self.hasmarkup = should_do_markup(file)
         self._current_line = ""
-        self._terminal_width: Optional[int] = None
+        self._terminal_width: int | None = None
         self.code_highlight = True
 
     @property
@@ -101,14 +111,14 @@ def markup(self, text: str, **markup: bool) -> str:
         if self.hasmarkup:
             esc = [self._esctable[name] for name, on in markup.items() if on]
             if esc:
-                text = "".join("\x1b[%sm" % cod for cod in esc) + text + "\x1b[0m"
+                text = "".join(f"\x1b[{cod}m" for cod in esc) + text + "\x1b[0m"
         return text
 
     def sep(
         self,
         sepchar: str,
-        title: Optional[str] = None,
-        fullwidth: Optional[int] = None,
+        title: str | None = None,
+        fullwidth: int | None = None,
         **markup: bool,
     ) -> None:
         if fullwidth is None:
@@ -182,9 +192,7 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No
         """
         if indents and len(indents) != len(lines):
             raise ValueError(
-                "indents size ({}) should have same size as lines ({})".format(
-                    len(indents), len(lines)
-                )
+                f"indents size ({len(indents)}) should have same size as lines ({len(lines)})"
             )
         if not indents:
             indents = [""] * len(lines)
@@ -193,41 +201,54 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No
         for indent, new_line in zip(indents, new_lines):
             self.line(indent + new_line)
 
-    def _highlight(self, source: str) -> str:
-        """Highlight the given source code if we have markup support."""
+    def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer:
+        if lexer == "python":
+            return PythonLexer()
+        elif lexer == "diff":
+            return DiffLexer()
+        else:
+            assert_never(lexer)
+
+    def _get_pygments_formatter(self) -> TerminalFormatter:
         from _pytest.config.exceptions import UsageError
 
-        if not self.hasmarkup or not self.code_highlight:
-            return source
+        theme = os.getenv("PYTEST_THEME")
+        theme_mode = os.getenv("PYTEST_THEME_MODE", "dark")
+
         try:
-            from pygments.formatters.terminal import TerminalFormatter
-            from pygments.lexers.python import PythonLexer
-            from pygments import highlight
-            import pygments.util
-        except ImportError:
+            return TerminalFormatter(bg=theme_mode, style=theme)
+        except pygments.util.ClassNotFound as e:
+            raise UsageError(
+                f"PYTEST_THEME environment variable has an invalid value: '{theme}'. "
+                "Hint: See available pygments styles with `pygmentize -L styles`."
+            ) from e
+        except pygments.util.OptionError as e:
+            raise UsageError(
+                f"PYTEST_THEME_MODE environment variable has an invalid value: '{theme_mode}'. "
+                "The allowed values are 'dark' (default) and 'light'."
+            ) from e
+
+    def _highlight(
+        self, source: str, lexer: Literal["diff", "python"] = "python"
+    ) -> str:
+        """Highlight the given source if we have markup support."""
+        if not source or not self.hasmarkup or not self.code_highlight:
             return source
-        else:
-            try:
-                highlighted: str = highlight(
-                    source,
-                    PythonLexer(),
-                    TerminalFormatter(
-                        bg=os.getenv("PYTEST_THEME_MODE", "dark"),
-                        style=os.getenv("PYTEST_THEME"),
-                    ),
-                )
-                return highlighted
-            except pygments.util.ClassNotFound:
-                raise UsageError(
-                    "PYTEST_THEME environment variable had an invalid value: '{}'. "
-                    "Only valid pygment styles are allowed.".format(
-                        os.getenv("PYTEST_THEME")
-                    )
-                )
-            except pygments.util.OptionError:
-                raise UsageError(
-                    "PYTEST_THEME_MODE environment variable had an invalid value: '{}'. "
-                    "The only allowed values are 'dark' and 'light'.".format(
-                        os.getenv("PYTEST_THEME_MODE")
-                    )
-                )
+
+        pygments_lexer = self._get_pygments_lexer(lexer)
+        pygments_formatter = self._get_pygments_formatter()
+
+        highlighted: str = pygments.highlight(
+            source, pygments_lexer, pygments_formatter
+        )
+        # pygments terminal formatter may add a newline when there wasn't one.
+        # We don't want this, remove.
+        if highlighted[-1] == "\n" and source[-1] != "\n":
+            highlighted = highlighted[:-1]
+
+        # Some lexers will not set the initial color explicitly
+        # which may lead to the previous color being propagated to the
+        # start of the expression, so reset first.
+        highlighted = "\x1b[0m" + highlighted
+
+        return highlighted
diff --git a/src/_pytest/_io/wcwidth.py b/src/_pytest/_io/wcwidth.py
index e5c7bf4d868..23886ff1581 100644
--- a/src/_pytest/_io/wcwidth.py
+++ b/src/_pytest/_io/wcwidth.py
@@ -1,5 +1,7 @@
-import unicodedata
+from __future__ import annotations
+
 from functools import lru_cache
+import unicodedata
 
 
 @lru_cache(100)
diff --git a/src/_pytest/_py/__init__.py b/src/_pytest/_py/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/src/_pytest/_py/error.py b/src/_pytest/_py/error.py
new file mode 100644
index 00000000000..dace23764ff
--- /dev/null
+++ b/src/_pytest/_py/error.py
@@ -0,0 +1,119 @@
+"""create errno-specific classes for IO or os calls."""
+
+from __future__ import annotations
+
+from collections.abc import Callable
+import errno
+import os
+import sys
+from typing import TYPE_CHECKING
+from typing import TypeVar
+
+
+if TYPE_CHECKING:
+    from typing_extensions import ParamSpec
+
+    P = ParamSpec("P")
+
+R = TypeVar("R")
+
+
+class Error(EnvironmentError):
+    def __repr__(self) -> str:
+        return "{}.{} {!r}: {} ".format(
+            self.__class__.__module__,
+            self.__class__.__name__,
+            self.__class__.__doc__,
+            " ".join(map(str, self.args)),
+            # repr(self.args)
+        )
+
+    def __str__(self) -> str:
+        s = "[{}]: {}".format(
+            self.__class__.__doc__,
+            " ".join(map(str, self.args)),
+        )
+        return s
+
+
+_winerrnomap = {
+    2: errno.ENOENT,
+    3: errno.ENOENT,
+    17: errno.EEXIST,
+    18: errno.EXDEV,
+    13: errno.EBUSY,  # empty cd drive, but ENOMEDIUM seems unavailable
+    22: errno.ENOTDIR,
+    20: errno.ENOTDIR,
+    267: errno.ENOTDIR,
+    5: errno.EACCES,  # anything better?
+}
+
+
+class ErrorMaker:
+    """lazily provides Exception classes for each possible POSIX errno
+    (as defined per the 'errno' module).  All such instances
+    subclass EnvironmentError.
+    """
+
+    _errno2class: dict[int, type[Error]] = {}
+
+    def __getattr__(self, name: str) -> type[Error]:
+        if name[0] == "_":
+            raise AttributeError(name)
+        eno = getattr(errno, name)
+        cls = self._geterrnoclass(eno)
+        setattr(self, name, cls)
+        return cls
+
+    def _geterrnoclass(self, eno: int) -> type[Error]:
+        try:
+            return self._errno2class[eno]
+        except KeyError:
+            clsname = errno.errorcode.get(eno, f"UnknownErrno{eno}")
+            errorcls = type(
+                clsname,
+                (Error,),
+                {"__module__": "py.error", "__doc__": os.strerror(eno)},
+            )
+            self._errno2class[eno] = errorcls
+            return errorcls
+
+    def checked_call(
+        self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs
+    ) -> R:
+        """Call a function and raise an errno-exception if applicable."""
+        __tracebackhide__ = True
+        try:
+            return func(*args, **kwargs)
+        except Error:
+            raise
+        except OSError as value:
+            if not hasattr(value, "errno"):
+                raise
+            if sys.platform == "win32":
+                try:
+                    # error: Invalid index type "Optional[int]" for "dict[int, int]"; expected type "int"  [index]
+                    # OK to ignore because we catch the KeyError below.
+                    cls = self._geterrnoclass(_winerrnomap[value.errno])  # type:ignore[index]
+                except KeyError:
+                    raise value
+            else:
+                # we are not on Windows, or we got a proper OSError
+                if value.errno is None:
+                    cls = type(
+                        "UnknownErrnoNone",
+                        (Error,),
+                        {"__module__": "py.error", "__doc__": None},
+                    )
+                else:
+                    cls = self._geterrnoclass(value.errno)
+
+            raise cls(f"{func.__name__}{args!r}")
+
+
+_error_maker = ErrorMaker()
+checked_call = _error_maker.checked_call
+
+
+def __getattr__(attr: str) -> type[Error]:
+    return getattr(_error_maker, attr)  # type: ignore[no-any-return]
diff --git a/src/_pytest/_py/path.py b/src/_pytest/_py/path.py
new file mode 100644
index 00000000000..e353c1a9b52
--- /dev/null
+++ b/src/_pytest/_py/path.py
@@ -0,0 +1,1475 @@
+# mypy: allow-untyped-defs
+"""local path implementation."""
+
+from __future__ import annotations
+
+import atexit
+from collections.abc import Callable
+from contextlib import contextmanager
+import fnmatch
+import importlib.util
+import io
+import os
+from os.path import abspath
+from os.path import dirname
+from os.path import exists
+from os.path import isabs
+from os.path import isdir
+from os.path import isfile
+from os.path import islink
+from os.path import normpath
+import posixpath
+from stat import S_ISDIR
+from stat import S_ISLNK
+from stat import S_ISREG
+import sys
+from typing import Any
+from typing import cast
+from typing import Literal
+from typing import overload
+from typing import TYPE_CHECKING
+import uuid
+import warnings
+
+from . import error
+
+
+# Moved from local.py.
+iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt")
+
+
+class Checkers:
+    _depend_on_existence = "exists", "link", "dir", "file"
+
+    def __init__(self, path):
+        self.path = path
+
+    def dotfile(self):
+        return self.path.basename.startswith(".")
+
+    def ext(self, arg):
+        if not arg.startswith("."):
+            arg = "." + arg
+        return self.path.ext == arg
+
+    def basename(self, arg):
+        return self.path.basename == arg
+
+    def basestarts(self, arg):
+        return self.path.basename.startswith(arg)
+
+    def relto(self, arg):
+        return self.path.relto(arg)
+
+    def fnmatch(self, arg):
+        return self.path.fnmatch(arg)
+
+    def endswith(self, arg):
+        return str(self.path).endswith(arg)
+
+    def _evaluate(self, kw):
+        from .._code.source import getrawcode
+
+        for name, value in kw.items():
+            invert = False
+            meth = None
+            try:
+                meth = getattr(self, name)
+            except AttributeError:
+                if name[:3] == "not":
+                    invert = True
+                    try:
+                        meth = getattr(self, name[3:])
+                    except AttributeError:
+                        pass
+            if meth is None:
+                raise TypeError(f"no {name!r} checker available for {self.path!r}")
+            try:
+                if getrawcode(meth).co_argcount > 1:
+                    if (not meth(value)) ^ invert:
+                        return False
+                else:
+                    if bool(value) ^ bool(meth()) ^ invert:
+                        return False
+            except (error.ENOENT, error.ENOTDIR, error.EBUSY):
+                # EBUSY feels not entirely correct,
+                # but its kind of necessary since ENOMEDIUM
+                # is not accessible in python
+                for name in self._depend_on_existence:
+                    if name in kw:
+                        if kw.get(name):
+                            return False
+                    name = "not" + name
+                    if name in kw:
+                        if not kw.get(name):
+                            return False
+        return True
+
+    _statcache: Stat
+
+    def _stat(self) -> Stat:
+        try:
+            return self._statcache
+        except AttributeError:
+            try:
+                self._statcache = self.path.stat()
+            except error.ELOOP:
+                self._statcache = self.path.lstat()
+            return self._statcache
+
+    def dir(self):
+        return S_ISDIR(self._stat().mode)
+
+    def file(self):
+        return S_ISREG(self._stat().mode)
+
+    def exists(self):
+        return self._stat()
+
+    def link(self):
+        st = self.path.lstat()
+        return S_ISLNK(st.mode)
+
+
+class NeverRaised(Exception):
+    pass
+
+
+class Visitor:
+    def __init__(self, fil, rec, ignore, bf, sort):
+        if isinstance(fil, str):
+            fil = FNMatcher(fil)
+        if isinstance(rec, str):
+            self.rec: Callable[[LocalPath], bool] = FNMatcher(rec)
+        elif not hasattr(rec, "__call__") and rec:
+            self.rec = lambda path: True
+        else:
+            self.rec = rec
+        self.fil = fil
+        self.ignore = ignore
+        self.breadthfirst = bf
+        self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x)
+
+    def gen(self, path):
+        try:
+            entries = path.listdir()
+        except self.ignore:
+            return
+        rec = self.rec
+        dirs = self.optsort(
+            [p for p in entries if p.check(dir=1) and (rec is None or rec(p))]
+        )
+        if not self.breadthfirst:
+            for subdir in dirs:
+                yield from self.gen(subdir)
+        for p in self.optsort(entries):
+            if self.fil is None or self.fil(p):
+                yield p
+        if self.breadthfirst:
+            for subdir in dirs:
+                yield from self.gen(subdir)
+
+
+class FNMatcher:
+    def __init__(self, pattern):
+        self.pattern = pattern
+
+    def __call__(self, path):
+        pattern = self.pattern
+
+        if (
+            pattern.find(path.sep) == -1
+            and iswin32
+            and pattern.find(posixpath.sep) != -1
+        ):
+            # Running on Windows, the pattern has no Windows path separators,
+            # and the pattern has one or more Posix path separators. Replace
+            # the Posix path separators with the Windows path separator.
+            pattern = pattern.replace(posixpath.sep, path.sep)
+
+        if pattern.find(path.sep) == -1:
+            name = path.basename
+        else:
+            name = str(path)  # path.strpath # XXX svn?
+            if not os.path.isabs(pattern):
+                pattern = "*" + path.sep + pattern
+        return fnmatch.fnmatch(name, pattern)
+
+
+def map_as_list(func, iter):
+    return list(map(func, iter))
+
+
+class Stat:
+    if TYPE_CHECKING:
+
+        @property
+        def size(self) -> int: ...
+
+        @property
+        def mtime(self) -> float: ...
+
+    def __getattr__(self, name: str) -> Any:
+        return getattr(self._osstatresult, "st_" + name)
+
+    def __init__(self, path, osstatresult):
+        self.path = path
+        self._osstatresult = osstatresult
+
+    @property
+    def owner(self):
+        if iswin32:
+            raise NotImplementedError("XXX win32")
+        import pwd
+
+        entry = error.checked_call(pwd.getpwuid, self.uid)  # type:ignore[attr-defined,unused-ignore]
+        return entry[0]
+
+    @property
+    def group(self):
+        """Return group name of file."""
+        if iswin32:
+            raise NotImplementedError("XXX win32")
+        import grp
+
+        entry = error.checked_call(grp.getgrgid, self.gid)  # type:ignore[attr-defined,unused-ignore]
+        return entry[0]
+
+    def isdir(self):
+        return S_ISDIR(self._osstatresult.st_mode)
+
+    def isfile(self):
+        return S_ISREG(self._osstatresult.st_mode)
+
+    def islink(self):
+        self.path.lstat()
+        return S_ISLNK(self._osstatresult.st_mode)
+
+
+def getuserid(user):
+    import pwd
+
+    if not isinstance(user, int):
+        user = pwd.getpwnam(user)[2]  # type:ignore[attr-defined,unused-ignore]
+    return user
+
+
+def getgroupid(group):
+    import grp
+
+    if not isinstance(group, int):
+        group = grp.getgrnam(group)[2]  # type:ignore[attr-defined,unused-ignore]
+    return group
+
+
+class LocalPath:
+    """Object oriented interface to os.path and other local filesystem
+    related information.
+    """
+
+    class ImportMismatchError(ImportError):
+        """raised on pyimport() if there is a mismatch of __file__'s"""
+
+    sep = os.sep
+
+    def __init__(self, path=None, expanduser=False):
+        """Initialize and return a local Path instance.
+
+        Path can be relative to the current directory.
+        If path is None it defaults to the current working directory.
+        If expanduser is True, tilde-expansion is performed.
+        Note that Path instances always carry an absolute path.
+        Note also that passing in a local path object will simply return
+        the exact same path object. Use new() to get a new copy.
+        """
+        if path is None:
+            self.strpath = error.checked_call(os.getcwd)
+        else:
+            try:
+                path = os.fspath(path)
+            except TypeError:
+                raise ValueError(
+                    "can only pass None, Path instances "
+                    "or non-empty strings to LocalPath"
+                )
+            if expanduser:
+                path = os.path.expanduser(path)
+            self.strpath = abspath(path)
+
+    if sys.platform != "win32":
+
+        def chown(self, user, group, rec=0):
+            """Change ownership to the given user and group.
+            user and group may be specified by a number or
+            by a name.  if rec is True change ownership
+            recursively.
+            """
+            uid = getuserid(user)
+            gid = getgroupid(group)
+            if rec:
+                for x in self.visit(rec=lambda x: x.check(link=0)):
+                    if x.check(link=0):
+                        error.checked_call(os.chown, str(x), uid, gid)
+            error.checked_call(os.chown, str(self), uid, gid)
+
+        def readlink(self) -> str:
+            """Return value of a symbolic link."""
+            # https://github.com/python/mypy/issues/12278
+            return error.checked_call(os.readlink, self.strpath)  # type: ignore[arg-type,return-value,unused-ignore]
+
+        def mklinkto(self, oldname):
+            """Posix style hard link to another name."""
+            error.checked_call(os.link, str(oldname), str(self))
+
+        def mksymlinkto(self, value, absolute=1):
+            """Create a symbolic link with the given value (pointing to another name)."""
+            if absolute:
+                error.checked_call(os.symlink, str(value), self.strpath)
+            else:
+                base = self.common(value)
+                # with posix local paths '/' is always a common base
+                relsource = self.__class__(value).relto(base)
+                reldest = self.relto(base)
+                n = reldest.count(self.sep)
+                target = self.sep.join(("..",) * n + (relsource,))
+                error.checked_call(os.symlink, target, self.strpath)
+
+    def __div__(self, other):
+        return self.join(os.fspath(other))
+
+    __truediv__ = __div__  # py3k
+
+    @property
+    def basename(self):
+        """Basename part of path."""
+        return self._getbyspec("basename")[0]
+
+    @property
+    def dirname(self):
+        """Dirname part of path."""
+        return self._getbyspec("dirname")[0]
+
+    @property
+    def purebasename(self):
+        """Pure base name of the path."""
+        return self._getbyspec("purebasename")[0]
+
+    @property
+    def ext(self):
+        """Extension of the path (including the '.')."""
+        return self._getbyspec("ext")[0]
+
+    def read_binary(self):
+        """Read and return a bytestring from reading the path."""
+        with self.open("rb") as f:
+            return f.read()
+
+    def read_text(self, encoding):
+        """Read and return a Unicode string from reading the path."""
+        with self.open("r", encoding=encoding) as f:
+            return f.read()
+
+    def read(self, mode="r"):
+        """Read and return a bytestring from reading the path."""
+        with self.open(mode) as f:
+            return f.read()
+
+    def readlines(self, cr=1):
+        """Read and return a list of lines from the path. if cr is False, the
+        newline will be removed from the end of each line."""
+        mode = "r"
+
+        if not cr:
+            content = self.read(mode)
+            return content.split("\n")
+        else:
+            f = self.open(mode)
+            try:
+                return f.readlines()
+            finally:
+                f.close()
+
+    def load(self):
+        """(deprecated) return object unpickled from self.read()"""
+        f = self.open("rb")
+        try:
+            import pickle
+
+            return error.checked_call(pickle.load, f)
+        finally:
+            f.close()
+
+    def move(self, target):
+        """Move this path to target."""
+        if target.relto(self):
+            raise error.EINVAL(target, "cannot move path into a subdirectory of itself")
+        try:
+            self.rename(target)
+        except error.EXDEV:  # invalid cross-device link
+            self.copy(target)
+            self.remove()
+
+    def fnmatch(self, pattern):
+        """Return true if the basename/fullname matches the glob-'pattern'.
+
+        valid pattern characters::
+
+            *       matches everything
+            ?       matches any single character
+            [seq]   matches any character in seq
+            [!seq]  matches any char not in seq
+
+        If the pattern contains a path-separator then the full path
+        is used for pattern matching and a '*' is prepended to the
+        pattern.
+
+        if the pattern doesn't contain a path-separator the pattern
+        is only matched against the basename.
+        """
+        return FNMatcher(pattern)(self)
+
+    def relto(self, relpath):
+        """Return a string which is the relative part of the path
+        to the given 'relpath'.
+        """
+        if not isinstance(relpath, (str, LocalPath)):
+            raise TypeError(f"{relpath!r}: not a string or path object")
+        strrelpath = str(relpath)
+        if strrelpath and strrelpath[-1] != self.sep:
+            strrelpath += self.sep
+        # assert strrelpath[-1] == self.sep
+        # assert strrelpath[-2] != self.sep
+        strself = self.strpath
+        if sys.platform == "win32" or getattr(os, "_name", None) == "nt":
+            if os.path.normcase(strself).startswith(os.path.normcase(strrelpath)):
+                return strself[len(strrelpath) :]
+        elif strself.startswith(strrelpath):
+            return strself[len(strrelpath) :]
+        return ""
+
+    def ensure_dir(self, *args):
+        """Ensure the path joined with args is a directory."""
+        return self.ensure(*args, dir=True)
+
+    def bestrelpath(self, dest):
+        """Return a string which is a relative path from self
+        (assumed to be a directory) to dest such that
+        self.join(bestrelpath) == dest and if not such
+        path can be determined return dest.
+        """
+        try:
+            if self == dest:
+                return os.curdir
+            base = self.common(dest)
+            if not base:  # can be the case on windows
+                return str(dest)
+            self2base = self.relto(base)
+            reldest = dest.relto(base)
+            if self2base:
+                n = self2base.count(self.sep) + 1
+            else:
+                n = 0
+            lst = [os.pardir] * n
+            if reldest:
+                lst.append(reldest)
+            target = dest.sep.join(lst)
+            return target
+        except AttributeError:
+            return str(dest)
+
+    def exists(self):
+        return self.check()
+
+    def isdir(self):
+        return self.check(dir=1)
+
+    def isfile(self):
+        return self.check(file=1)
+
+    def parts(self, reverse=False):
+        """Return a root-first list of all ancestor directories
+        plus the path itself.
+        """
+        current = self
+        lst = [self]
+        while 1:
+            last = current
+            current = current.dirpath()
+            if last == current:
+                break
+            lst.append(current)
+        if not reverse:
+            lst.reverse()
+        return lst
+
+    def common(self, other):
+        """Return the common part shared with the other path
+        or None if there is no common part.
+        """
+        last = None
+        for x, y in zip(self.parts(), other.parts()):
+            if x != y:
+                return last
+            last = x
+        return last
+
+    def __add__(self, other):
+        """Return new path object with 'other' added to the basename"""
+        return self.new(basename=self.basename + str(other))
+
+    def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False):
+        """Yields all paths below the current one
+
+        fil is a filter (glob pattern or callable), if not matching the
+        path will not be yielded, defaulting to None (everything is
+        returned)
+
+        rec is a filter (glob pattern or callable) that controls whether
+        a node is descended, defaulting to None
+
+        ignore is an Exception class that is ignoredwhen calling dirlist()
+        on any of the paths (by default, all exceptions are reported)
+
+        bf if True will cause a breadthfirst search instead of the
+        default depthfirst. Default: False
+
+        sort if True will sort entries within each directory level.
+        """
+        yield from Visitor(fil, rec, ignore, bf, sort).gen(self)
+
+    def _sortlist(self, res, sort):
+        if sort:
+            if hasattr(sort, "__call__"):
+                warnings.warn(
+                    DeprecationWarning(
+                        "listdir(sort=callable) is deprecated and breaks on python3"
+                    ),
+                    stacklevel=3,
+                )
+                res.sort(sort)
+            else:
+                res.sort()
+
+    def __fspath__(self):
+        return self.strpath
+
+    def __hash__(self):
+        s = self.strpath
+        if iswin32:
+            s = s.lower()
+        return hash(s)
+
+    def __eq__(self, other):
+        s1 = os.fspath(self)
+        try:
+            s2 = os.fspath(other)
+        except TypeError:
+            return False
+        if iswin32:
+            s1 = s1.lower()
+            try:
+                s2 = s2.lower()
+            except AttributeError:
+                return False
+        return s1 == s2
+
+    def __ne__(self, other):
+        return not (self == other)
+
+    def __lt__(self, other):
+        return os.fspath(self) < os.fspath(other)
+
+    def __gt__(self, other):
+        return os.fspath(self) > os.fspath(other)
+
+    def samefile(self, other):
+        """Return True if 'other' references the same file as 'self'."""
+        other = os.fspath(other)
+        if not isabs(other):
+            other = abspath(other)
+        if self == other:
+            return True
+        if not hasattr(os.path, "samefile"):
+            return False
+        return error.checked_call(os.path.samefile, self.strpath, other)
+
+    def remove(self, rec=1, ignore_errors=False):
+        """Remove a file or directory (or a directory tree if rec=1).
+        if ignore_errors is True, errors while removing directories will
+        be ignored.
+        """
+        if self.check(dir=1, link=0):
+            if rec:
+                # force remove of readonly files on windows
+                if iswin32:
+                    self.chmod(0o700, rec=1)
+                import shutil
+
+                error.checked_call(
+                    shutil.rmtree, self.strpath, ignore_errors=ignore_errors
+                )
+            else:
+                error.checked_call(os.rmdir, self.strpath)
+        else:
+            if iswin32:
+                self.chmod(0o700)
+            error.checked_call(os.remove, self.strpath)
+
+    def computehash(self, hashtype="md5", chunksize=524288):
+        """Return hexdigest of hashvalue for this file."""
+        try:
+            try:
+                import hashlib as mod
+            except ImportError:
+                if hashtype == "sha1":
+                    hashtype = "sha"
+                mod = __import__(hashtype)
+            hash = getattr(mod, hashtype)()
+        except (AttributeError, ImportError):
+            raise ValueError(f"Don't know how to compute {hashtype!r} hash")
+        f = self.open("rb")
+        try:
+            while 1:
+                buf = f.read(chunksize)
+                if not buf:
+                    return hash.hexdigest()
+                hash.update(buf)
+        finally:
+            f.close()
+
+    def new(self, **kw):
+        """Create a modified version of this path.
+        the following keyword arguments modify various path parts::
+
+          a:/some/path/to/a/file.ext
+          xx                           drive
+          xxxxxxxxxxxxxxxxx            dirname
+                            xxxxxxxx   basename
+                            xxxx       purebasename
+                                 xxx   ext
+        """
+        obj = object.__new__(self.__class__)
+        if not kw:
+            obj.strpath = self.strpath
+            return obj
+        drive, dirname, basename, purebasename, ext = self._getbyspec(
+            "drive,dirname,basename,purebasename,ext"
+        )
+        if "basename" in kw:
+            if "purebasename" in kw or "ext" in kw:
+                raise ValueError(f"invalid specification {kw!r}")
+        else:
+            pb = kw.setdefault("purebasename", purebasename)
+            try:
+                ext = kw["ext"]
+            except KeyError:
+                pass
+            else:
+                if ext and not ext.startswith("."):
+                    ext = "." + ext
+            kw["basename"] = pb + ext
+
+        if "dirname" in kw and not kw["dirname"]:
+            kw["dirname"] = drive
+        else:
+            kw.setdefault("dirname", dirname)
+        kw.setdefault("sep", self.sep)
+        obj.strpath = normpath("{dirname}{sep}{basename}".format(**kw))
+        return obj
+
+    def _getbyspec(self, spec: str) -> list[str]:
+        """See new for what 'spec' can be."""
+        res = []
+        parts = self.strpath.split(self.sep)
+
+        args = filter(None, spec.split(","))
+        for name in args:
+            if name == "drive":
+                res.append(parts[0])
+            elif name == "dirname":
+                res.append(self.sep.join(parts[:-1]))
+            else:
+                basename = parts[-1]
+                if name == "basename":
+                    res.append(basename)
+                else:
+                    i = basename.rfind(".")
+                    if i == -1:
+                        purebasename, ext = basename, ""
+                    else:
+                        purebasename, ext = basename[:i], basename[i:]
+                    if name == "purebasename":
+                        res.append(purebasename)
+                    elif name == "ext":
+                        res.append(ext)
+                    else:
+                        raise ValueError(f"invalid part specification {name!r}")
+        return res
+
+    def dirpath(self, *args, **kwargs):
+        """Return the directory path joined with any given path arguments."""
+        if not kwargs:
+            path = object.__new__(self.__class__)
+            path.strpath = dirname(self.strpath)
+            if args:
+                path = path.join(*args)
+            return path
+        return self.new(basename="").join(*args, **kwargs)
+
+    def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath:
+        """Return a new path by appending all 'args' as path
+        components.  if abs=1 is used restart from root if any
+        of the args is an absolute path.
+        """
+        sep = self.sep
+        strargs = [os.fspath(arg) for arg in args]
+        strpath = self.strpath
+        if abs:
+            newargs: list[str] = []
+            for arg in reversed(strargs):
+                if isabs(arg):
+                    strpath = arg
+                    strargs = newargs
+                    break
+                newargs.insert(0, arg)
+        # special case for when we have e.g. strpath == "/"
+        actual_sep = "" if strpath.endswith(sep) else sep
+        for arg in strargs:
+            arg = arg.strip(sep)
+            if iswin32:
+                # allow unix style paths even on windows.
+                arg = arg.strip("/")
+                arg = arg.replace("/", sep)
+            strpath = strpath + actual_sep + arg
+            actual_sep = sep
+        obj = object.__new__(self.__class__)
+        obj.strpath = normpath(strpath)
+        return obj
+
+    def open(self, mode="r", ensure=False, encoding=None):
+        """Return an opened file with the given mode.
+
+        If ensure is True, create parent directories if needed.
+        """
+        if ensure:
+            self.dirpath().ensure(dir=1)
+        if encoding:
+            return error.checked_call(
+                io.open,
+                self.strpath,
+                mode,
+                encoding=encoding,
+            )
+        return error.checked_call(open, self.strpath, mode)
+
+    def _fastjoin(self, name):
+        child = object.__new__(self.__class__)
+        child.strpath = self.strpath + self.sep + name
+        return child
+
+    def islink(self):
+        return islink(self.strpath)
+
+    def check(self, **kw):
+        """Check a path for existence and properties.
+
+        Without arguments, return True if the path exists, otherwise False.
+
+        valid checkers::
+
+            file = 1  # is a file
+            file = 0  # is not a file (may not even exist)
+            dir = 1  # is a dir
+            link = 1  # is a link
+            exists = 1  # exists
+
+        You can specify multiple checker definitions, for example::
+
+            path.check(file=1, link=1)  # a link pointing to a file
+        """
+        if not kw:
+            return exists(self.strpath)
+        if len(kw) == 1:
+            if "dir" in kw:
+                return not kw["dir"] ^ isdir(self.strpath)
+            if "file" in kw:
+                return not kw["file"] ^ isfile(self.strpath)
+        if not kw:
+            kw = {"exists": 1}
+        return Checkers(self)._evaluate(kw)
+
+    _patternchars = set("*?[" + os.sep)
+
+    def listdir(self, fil=None, sort=None):
+        """List directory contents, possibly filter by the given fil func
+        and possibly sorted.
+        """
+        if fil is None and sort is None:
+            names = error.checked_call(os.listdir, self.strpath)
+            return map_as_list(self._fastjoin, names)
+        if isinstance(fil, str):
+            if not self._patternchars.intersection(fil):
+                child = self._fastjoin(fil)
+                if exists(child.strpath):
+                    return [child]
+                return []
+            fil = FNMatcher(fil)
+        names = error.checked_call(os.listdir, self.strpath)
+        res = []
+        for name in names:
+            child = self._fastjoin(name)
+            if fil is None or fil(child):
+                res.append(child)
+        self._sortlist(res, sort)
+        return res
+
+    def size(self) -> int:
+        """Return size of the underlying file object"""
+        return self.stat().size
+
+    def mtime(self) -> float:
+        """Return last modification time of the path."""
+        return self.stat().mtime
+
+    def copy(self, target, mode=False, stat=False):
+        """Copy path to target.
+
+        If mode is True, will copy permission from path to target.
+        If stat is True, copy permission, last modification
+        time, last access time, and flags from path to target.
+        """
+        if self.check(file=1):
+            if target.check(dir=1):
+                target = target.join(self.basename)
+            assert self != target
+            copychunked(self, target)
+            if mode:
+                copymode(self.strpath, target.strpath)
+            if stat:
+                copystat(self, target)
+        else:
+
+            def rec(p):
+                return p.check(link=0)
+
+            for x in self.visit(rec=rec):
+                relpath = x.relto(self)
+                newx = target.join(relpath)
+                newx.dirpath().ensure(dir=1)
+                if x.check(link=1):
+                    newx.mksymlinkto(x.readlink())
+                    continue
+                elif x.check(file=1):
+                    copychunked(x, newx)
+                elif x.check(dir=1):
+                    newx.ensure(dir=1)
+                if mode:
+                    copymode(x.strpath, newx.strpath)
+                if stat:
+                    copystat(x, newx)
+
+    def rename(self, target):
+        """Rename this path to target."""
+        target = os.fspath(target)
+        return error.checked_call(os.rename, self.strpath, target)
+
+    def dump(self, obj, bin=1):
+        """Pickle object into path location"""
+        f = self.open("wb")
+        import pickle
+
+        try:
+            error.checked_call(pickle.dump, obj, f, bin)
+        finally:
+            f.close()
+
+    def mkdir(self, *args):
+        """Create & return the directory joined with args."""
+        p = self.join(*args)
+        error.checked_call(os.mkdir, os.fspath(p))
+        return p
+
+    def write_binary(self, data, ensure=False):
+        """Write binary data into path.   If ensure is True create
+        missing parent directories.
+        """
+        if ensure:
+            self.dirpath().ensure(dir=1)
+        with self.open("wb") as f:
+            f.write(data)
+
+    def write_text(self, data, encoding, ensure=False):
+        """Write text data into path using the specified encoding.
+        If ensure is True create missing parent directories.
+        """
+        if ensure:
+            self.dirpath().ensure(dir=1)
+        with self.open("w", encoding=encoding) as f:
+            f.write(data)
+
+    def write(self, data, mode="w", ensure=False):
+        """Write data into path.   If ensure is True create
+        missing parent directories.
+        """
+        if ensure:
+            self.dirpath().ensure(dir=1)
+        if "b" in mode:
+            if not isinstance(data, bytes):
+                raise ValueError("can only process bytes")
+        else:
+            if not isinstance(data, str):
+                if not isinstance(data, bytes):
+                    data = str(data)
+                else:
+                    data = data.decode(sys.getdefaultencoding())
+        f = self.open(mode)
+        try:
+            f.write(data)
+        finally:
+            f.close()
+
+    def _ensuredirs(self):
+        parent = self.dirpath()
+        if parent == self:
+            return self
+        if parent.check(dir=0):
+            parent._ensuredirs()
+        if self.check(dir=0):
+            try:
+                self.mkdir()
+            except error.EEXIST:
+                # race condition: file/dir created by another thread/process.
+                # complain if it is not a dir
+                if self.check(dir=0):
+                    raise
+        return self
+
+    def ensure(self, *args, **kwargs):
+        """Ensure that an args-joined path exists (by default as
+        a file). if you specify a keyword argument 'dir=True'
+        then the path is forced to be a directory path.
+        """
+        p = self.join(*args)
+        if kwargs.get("dir", 0):
+            return p._ensuredirs()
+        else:
+            p.dirpath()._ensuredirs()
+            if not p.check(file=1):
+                p.open("wb").close()
+            return p
+
+    @overload
+    def stat(self, raising: Literal[True] = ...) -> Stat: ...
+
+    @overload
+    def stat(self, raising: Literal[False]) -> Stat | None: ...
+
+    def stat(self, raising: bool = True) -> Stat | None:
+        """Return an os.stat() tuple."""
+        if raising:
+            return Stat(self, error.checked_call(os.stat, self.strpath))
+        try:
+            return Stat(self, os.stat(self.strpath))
+        except KeyboardInterrupt:
+            raise
+        except Exception:
+            return None
+
+    def lstat(self) -> Stat:
+        """Return an os.lstat() tuple."""
+        return Stat(self, error.checked_call(os.lstat, self.strpath))
+
+    def setmtime(self, mtime=None):
+        """Set modification time for the given path.  if 'mtime' is None
+        (the default) then the file's mtime is set to current time.
+
+        Note that the resolution for 'mtime' is platform dependent.
+        """
+        if mtime is None:
+            return error.checked_call(os.utime, self.strpath, mtime)
+        try:
+            return error.checked_call(os.utime, self.strpath, (-1, mtime))
+        except error.EINVAL:
+            return error.checked_call(os.utime, self.strpath, (self.atime(), mtime))
+
+    def chdir(self):
+        """Change directory to self and return old current directory"""
+        try:
+            old = self.__class__()
+        except error.ENOENT:
+            old = None
+        error.checked_call(os.chdir, self.strpath)
+        return old
+
+    @contextmanager
+    def as_cwd(self):
+        """
+        Return a context manager, which changes to the path's dir during the
+        managed "with" context.
+        On __enter__ it returns the old dir, which might be ``None``.
+        """
+        old = self.chdir()
+        try:
+            yield old
+        finally:
+            if old is not None:
+                old.chdir()
+
+    def realpath(self):
+        """Return a new path which contains no symbolic links."""
+        return self.__class__(os.path.realpath(self.strpath))
+
+    def atime(self):
+        """Return last access time of the path."""
+        return self.stat().atime
+
+    def __repr__(self):
+        return f"local({self.strpath!r})"
+
+    def __str__(self):
+        """Return string representation of the Path."""
+        return self.strpath
+
+    def chmod(self, mode, rec=0):
+        """Change permissions to the given mode. If mode is an
+        integer it directly encodes the os-specific modes.
+        if rec is True perform recursively.
+        """
+        if not isinstance(mode, int):
+            raise TypeError(f"mode {mode!r} must be an integer")
+        if rec:
+            for x in self.visit(rec=rec):
+                error.checked_call(os.chmod, str(x), mode)
+        error.checked_call(os.chmod, self.strpath, mode)
+
+    def pypkgpath(self):
+        """Return the Python package path by looking for the last
+        directory upwards which still contains an __init__.py.
+        Return None if a pkgpath cannot be determined.
+        """
+        pkgpath = None
+        for parent in self.parts(reverse=True):
+            if parent.isdir():
+                if not parent.join("__init__.py").exists():
+                    break
+                if not isimportable(parent.basename):
+                    break
+                pkgpath = parent
+        return pkgpath
+
+    def _ensuresyspath(self, ensuremode, path):
+        if ensuremode:
+            s = str(path)
+            if ensuremode == "append":
+                if s not in sys.path:
+                    sys.path.append(s)
+            else:
+                if s != sys.path[0]:
+                    sys.path.insert(0, s)
+
+    def pyimport(self, modname=None, ensuresyspath=True):
+        """Return path as an imported python module.
+
+        If modname is None, look for the containing package
+        and construct an according module name.
+        The module will be put/looked up in sys.modules.
+        if ensuresyspath is True then the root dir for importing
+        the file (taking __init__.py files into account) will
+        be prepended to sys.path if it isn't there already.
+        If ensuresyspath=="append" the root dir will be appended
+        if it isn't already contained in sys.path.
+        if ensuresyspath is False no modification of syspath happens.
+
+        Special value of ensuresyspath=="importlib" is intended
+        purely for using in pytest, it is capable only of importing
+        separate .py files outside packages, e.g. for test suite
+        without any __init__.py file. It effectively allows having
+        same-named test modules in different places and offers
+        mild opt-in via this option. Note that it works only in
+        recent versions of python.
+        """
+        if not self.check():
+            raise error.ENOENT(self)
+
+        if ensuresyspath == "importlib":
+            if modname is None:
+                modname = self.purebasename
+            spec = importlib.util.spec_from_file_location(modname, str(self))
+            if spec is None or spec.loader is None:
+                raise ImportError(f"Can't find module {modname} at location {self!s}")
+            mod = importlib.util.module_from_spec(spec)
+            spec.loader.exec_module(mod)
+            return mod
+
+        pkgpath = None
+        if modname is None:
+            pkgpath = self.pypkgpath()
+            if pkgpath is not None:
+                pkgroot = pkgpath.dirpath()
+                names = self.new(ext="").relto(pkgroot).split(self.sep)
+                if names[-1] == "__init__":
+                    names.pop()
+                modname = ".".join(names)
+            else:
+                pkgroot = self.dirpath()
+                modname = self.purebasename
+
+            self._ensuresyspath(ensuresyspath, pkgroot)
+            __import__(modname)
+            mod = sys.modules[modname]
+            if self.basename == "__init__.py":
+                return mod  # we don't check anything as we might
+                # be in a namespace package ... too icky to check
+            modfile = mod.__file__
+            assert modfile is not None
+            if modfile[-4:] in (".pyc", ".pyo"):
+                modfile = modfile[:-1]
+            elif modfile.endswith("$py.class"):
+                modfile = modfile[:-9] + ".py"
+            if modfile.endswith(os.sep + "__init__.py"):
+                if self.basename != "__init__.py":
+                    modfile = modfile[:-12]
+            try:
+                issame = self.samefile(modfile)
+            except error.ENOENT:
+                issame = False
+            if not issame:
+                ignore = os.getenv("PY_IGNORE_IMPORTMISMATCH")
+                if ignore != "1":
+                    raise self.ImportMismatchError(modname, modfile, self)
+            return mod
+        else:
+            try:
+                return sys.modules[modname]
+            except KeyError:
+                # we have a custom modname, do a pseudo-import
+                import types
+
+                mod = types.ModuleType(modname)
+                mod.__file__ = str(self)
+                sys.modules[modname] = mod
+                try:
+                    with open(str(self), "rb") as f:
+                        exec(f.read(), mod.__dict__)
+                except BaseException:
+                    del sys.modules[modname]
+                    raise
+                return mod
+
+    def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str:
+        """Return stdout text from executing a system child process,
+        where the 'self' path points to executable.
+        The process is directly invoked and not through a system shell.
+        """
+        from subprocess import PIPE
+        from subprocess import Popen
+
+        popen_opts.pop("stdout", None)
+        popen_opts.pop("stderr", None)
+        proc = Popen(
+            [str(self)] + [str(arg) for arg in argv],
+            **popen_opts,
+            stdout=PIPE,
+            stderr=PIPE,
+        )
+        stdout: str | bytes
+        stdout, stderr = proc.communicate()
+        ret = proc.wait()
+        if isinstance(stdout, bytes):
+            stdout = stdout.decode(sys.getdefaultencoding())
+        if ret != 0:
+            if isinstance(stderr, bytes):
+                stderr = stderr.decode(sys.getdefaultencoding())
+            raise RuntimeError(
+                ret,
+                ret,
+                str(self),
+                stdout,
+                stderr,
+            )
+        return stdout
+
+    @classmethod
+    def sysfind(cls, name, checker=None, paths=None):
+        """Return a path object found by looking at the systems
+        underlying PATH specification. If the checker is not None
+        it will be invoked to filter matching paths.  If a binary
+        cannot be found, None is returned
+        Note: This is probably not working on plain win32 systems
+        but may work on cygwin.
+        """
+        if isabs(name):
+            p = local(name)
+            if p.check(file=1):
+                return p
+        else:
+            if paths is None:
+                if iswin32:
+                    paths = os.environ["Path"].split(";")
+                    if "" not in paths and "." not in paths:
+                        paths.append(".")
+                    try:
+                        systemroot = os.environ["SYSTEMROOT"]
+                    except KeyError:
+                        pass
+                    else:
+                        paths = [
+                            path.replace("%SystemRoot%", systemroot) for path in paths
+                        ]
+                else:
+                    paths = os.environ["PATH"].split(":")
+            tryadd = []
+            if iswin32:
+                tryadd += os.environ["PATHEXT"].split(os.pathsep)
+            tryadd.append("")
+
+            for x in paths:
+                for addext in tryadd:
+                    p = local(x).join(name, abs=True) + addext
+                    try:
+                        if p.check(file=1):
+                            if checker:
+                                if not checker(p):
+                                    continue
+                            return p
+                    except error.EACCES:
+                        pass
+        return None
+
+    @classmethod
+    def _gethomedir(cls):
+        try:
+            x = os.environ["HOME"]
+        except KeyError:
+            try:
+                x = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"]
+            except KeyError:
+                return None
+        return cls(x)
+
+    # """
+    # special class constructors for local filesystem paths
+    # """
+    @classmethod
+    def get_temproot(cls):
+        """Return the system's temporary directory
+        (where tempfiles are usually created in)
+        """
+        import tempfile
+
+        return local(tempfile.gettempdir())
+
+    @classmethod
+    def mkdtemp(cls, rootdir=None):
+        """Return a Path object pointing to a fresh new temporary directory
+        (which we created ourselves).
+        """
+        import tempfile
+
+        if rootdir is None:
+            rootdir = cls.get_temproot()
+        path = error.checked_call(tempfile.mkdtemp, dir=str(rootdir))
+        return cls(path)
+
+    @classmethod
+    def make_numbered_dir(
+        cls, prefix="session-", rootdir=None, keep=3, lock_timeout=172800
+    ):  # two days
+        """Return unique directory with a number greater than the current
+        maximum one.  The number is assumed to start directly after prefix.
+        if keep is true directories with a number less than (maxnum-keep)
+        will be removed. If .lock files are used (lock_timeout non-zero),
+        algorithm is multi-process safe.
+        """
+        if rootdir is None:
+            rootdir = cls.get_temproot()
+
+        nprefix = prefix.lower()
+
+        def parse_num(path):
+            """Parse the number out of a path (if it matches the prefix)"""
+            nbasename = path.basename.lower()
+            if nbasename.startswith(nprefix):
+                try:
+                    return int(nbasename[len(nprefix) :])
+                except ValueError:
+                    pass
+
+        def create_lockfile(path):
+            """Exclusively create lockfile. Throws when failed"""
+            mypid = os.getpid()
+            lockfile = path.join(".lock")
+            if hasattr(lockfile, "mksymlinkto"):
+                lockfile.mksymlinkto(str(mypid))
+            else:
+                fd = error.checked_call(
+                    os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644
+                )
+                with os.fdopen(fd, "w") as f:
+                    f.write(str(mypid))
+            return lockfile
+
+        def atexit_remove_lockfile(lockfile):
+            """Ensure lockfile is removed at process exit"""
+            mypid = os.getpid()
+
+            def try_remove_lockfile():
+                # in a fork() situation, only the last process should
+                # remove the .lock, otherwise the other processes run the
+                # risk of seeing their temporary dir disappear.  For now
+                # we remove the .lock in the parent only (i.e. we assume
+                # that the children finish before the parent).
+                if os.getpid() != mypid:
+                    return
+                try:
+                    lockfile.remove()
+                except error.Error:
+                    pass
+
+            atexit.register(try_remove_lockfile)
+
+        # compute the maximum number currently in use with the prefix
+        lastmax = None
+        while True:
+            maxnum = -1
+            for path in rootdir.listdir():
+                num = parse_num(path)
+                if num is not None:
+                    maxnum = max(maxnum, num)
+
+            # make the new directory
+            try:
+                udir = rootdir.mkdir(prefix + str(maxnum + 1))
+                if lock_timeout:
+                    lockfile = create_lockfile(udir)
+                    atexit_remove_lockfile(lockfile)
+            except (error.EEXIST, error.ENOENT, error.EBUSY):
+                # race condition (1): another thread/process created the dir
+                #                     in the meantime - try again
+                # race condition (2): another thread/process spuriously acquired
+                #                     lock treating empty directory as candidate
+                #                     for removal - try again
+                # race condition (3): another thread/process tried to create the lock at
+                #                     the same time (happened in Python 3.3 on Windows)
+                # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa
+                if lastmax == maxnum:
+                    raise
+                lastmax = maxnum
+                continue
+            break
+
+        def get_mtime(path):
+            """Read file modification time"""
+            try:
+                return path.lstat().mtime
+            except error.Error:
+                pass
+
+        garbage_prefix = prefix + "garbage-"
+
+        def is_garbage(path):
+            """Check if path denotes directory scheduled for removal"""
+            bn = path.basename
+            return bn.startswith(garbage_prefix)
+
+        # prune old directories
+        udir_time = get_mtime(udir)
+        if keep and udir_time:
+            for path in rootdir.listdir():
+                num = parse_num(path)
+                if num is not None and num <= (maxnum - keep):
+                    try:
+                        # try acquiring lock to remove directory as exclusive user
+                        if lock_timeout:
+                            create_lockfile(path)
+                    except (error.EEXIST, error.ENOENT, error.EBUSY):
+                        path_time = get_mtime(path)
+                        if not path_time:
+                            # assume directory doesn't exist now
+                            continue
+                        if abs(udir_time - path_time) < lock_timeout:
+                            # assume directory with lockfile exists
+                            # and lock timeout hasn't expired yet
+                            continue
+
+                    # path dir locked for exclusive use
+                    # and scheduled for removal to avoid another thread/process
+                    # treating it as a new directory or removal candidate
+                    garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4()))
+                    try:
+                        path.rename(garbage_path)
+                        garbage_path.remove(rec=1)
+                    except KeyboardInterrupt:
+                        raise
+                    except Exception:  # this might be error.Error, WindowsError ...
+                        pass
+                if is_garbage(path):
+                    try:
+                        path.remove(rec=1)
+                    except KeyboardInterrupt:
+                        raise
+                    except Exception:  # this might be error.Error, WindowsError ...
+                        pass
+
+        # make link...
+        try:
+            username = os.environ["USER"]  # linux, et al
+        except KeyError:
+            try:
+                username = os.environ["USERNAME"]  # windows
+            except KeyError:
+                username = "current"
+
+        src = str(udir)
+        dest = src[: src.rfind("-")] + "-" + username
+        try:
+            os.unlink(dest)
+        except OSError:
+            pass
+        try:
+            os.symlink(src, dest)
+        except (OSError, AttributeError, NotImplementedError):
+            pass
+
+        return udir
+
+
+def copymode(src, dest):
+    """Copy permission from src to dst."""
+    import shutil
+
+    shutil.copymode(src, dest)
+
+
+def copystat(src, dest):
+    """Copy permission,  last modification time,
+    last access time, and flags from src to dst."""
+    import shutil
+
+    shutil.copystat(str(src), str(dest))
+
+
+def copychunked(src, dest):
+    chunksize = 524288  # half a meg of bytes
+    fsrc = src.open("rb")
+    try:
+        fdest = dest.open("wb")
+        try:
+            while 1:
+                buf = fsrc.read(chunksize)
+                if not buf:
+                    break
+                fdest.write(buf)
+        finally:
+            fdest.close()
+    finally:
+        fsrc.close()
+
+
+def isimportable(name):
+    if name and (name[0].isalpha() or name[0] == "_"):
+        name = name.replace("_", "")
+        return not name or name.isalnum()
+
+
+local = LocalPath
diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py
index 480a26ad867..532b96fe431 100644
--- a/src/_pytest/assertion/__init__.py
+++ b/src/_pytest/assertion/__init__.py
@@ -1,9 +1,11 @@
+# mypy: allow-untyped-defs
 """Support for presenting detailed information in failing assertions."""
+
+from __future__ import annotations
+
+from collections.abc import Generator
 import sys
 from typing import Any
-from typing import Generator
-from typing import List
-from typing import Optional
 from typing import TYPE_CHECKING
 
 from _pytest.assertion import rewrite
@@ -15,6 +17,7 @@
 from _pytest.config.argparsing import Parser
 from _pytest.nodes import Item
 
+
 if TYPE_CHECKING:
     from _pytest.main import Session
 
@@ -39,10 +42,30 @@ def pytest_addoption(parser: Parser) -> None:
         "enable_assertion_pass_hook",
         type="bool",
         default=False,
-        help="Enables the pytest_assertion_pass hook."
+        help="Enables the pytest_assertion_pass hook. "
         "Make sure to delete any previously generated pyc cache files.",
     )
 
+    parser.addini(
+        "truncation_limit_lines",
+        default=None,
+        help="Set threshold of LINES after which truncation will take effect",
+    )
+    parser.addini(
+        "truncation_limit_chars",
+        default=None,
+        help=("Set threshold of CHARS after which truncation will take effect"),
+    )
+
+    Config._add_verbosity_ini(
+        parser,
+        Config.VERBOSITY_ASSERTIONS,
+        help=(
+            "Specify a verbosity level for assertions, overriding the main level. "
+            "Higher levels will provide more detailed explanation when an assertion fails."
+        ),
+    )
+
 
 def register_assert_rewrite(*names: str) -> None:
     """Register one or more module names to be rewritten on import.
@@ -53,7 +76,7 @@ def register_assert_rewrite(*names: str) -> None:
     actually imported, usually in your __init__.py if you are a plugin
     using a package.
 
-    :raises TypeError: If the given module names are not strings.
+    :param names: The module names to register.
     """
     for name in names:
         if not isinstance(name, str):
@@ -83,7 +106,7 @@ class AssertionState:
     def __init__(self, config: Config, mode) -> None:
         self.mode = mode
         self.trace = config.trace.root.get("assertion")
-        self.hook: Optional[rewrite.AssertionRewritingHook] = None
+        self.hook: rewrite.AssertionRewritingHook | None = None
 
 
 def install_importhook(config: Config) -> rewrite.AssertionRewritingHook:
@@ -102,7 +125,7 @@ def undo() -> None:
     return hook
 
 
-def pytest_collection(session: "Session") -> None:
+def pytest_collection(session: Session) -> None:
     # This hook is only called when test modules are collected
     # so for example not in the managing process of pytest-xdist
     # (which does not collect test modules).
@@ -112,18 +135,17 @@ def pytest_collection(session: "Session") -> None:
             assertstate.hook.set_session(session)
 
 
-@hookimpl(tryfirst=True, hookwrapper=True)
-def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
+@hookimpl(wrapper=True, tryfirst=True)
+def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
     """Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks.
 
     The rewrite module will use util._reprcompare if it exists to use custom
     reporting via the pytest_assertrepr_compare hook.  This sets up this custom
     comparison for the test.
     """
-
     ihook = item.ihook
 
-    def callbinrepr(op, left: object, right: object) -> Optional[str]:
+    def callbinrepr(op, left: object, right: object) -> str | None:
         """Call the pytest_assertrepr_compare hook and prepare the result.
 
         This uses the first result from the hook and then ensures the
@@ -162,13 +184,14 @@ def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None:
 
         util._assertion_pass = call_assertion_pass_hook
 
-    yield
-
-    util._reprcompare, util._assertion_pass = saved_assert_hooks
-    util._config = None
+    try:
+        return (yield)
+    finally:
+        util._reprcompare, util._assertion_pass = saved_assert_hooks
+        util._config = None
 
 
-def pytest_sessionfinish(session: "Session") -> None:
+def pytest_sessionfinish(session: Session) -> None:
     assertstate = session.config.stash.get(assertstate_key, None)
     if assertstate:
         if assertstate.hook is not None:
@@ -177,5 +200,5 @@ def pytest_sessionfinish(session: "Session") -> None:
 
 def pytest_assertrepr_compare(
     config: Config, op: str, left: Any, right: Any
-) -> Optional[List[str]]:
+) -> list[str] | None:
     return util.assertrepr_compare(config=config, op=op, left=left, right=right)
diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py
index 456681ab29e..2e606d1903a 100644
--- a/src/_pytest/assertion/rewrite.py
+++ b/src/_pytest/assertion/rewrite.py
@@ -1,5 +1,13 @@
 """Rewrite assertion AST to produce nice error messages."""
+
+from __future__ import annotations
+
 import ast
+from collections import defaultdict
+from collections.abc import Callable
+from collections.abc import Iterable
+from collections.abc import Iterator
+from collections.abc import Sequence
 import errno
 import functools
 import importlib.abc
@@ -9,50 +17,50 @@
 import itertools
 import marshal
 import os
+from pathlib import Path
+from pathlib import PurePath
 import struct
 import sys
 import tokenize
 import types
-from pathlib import Path
-from pathlib import PurePath
-from typing import Callable
-from typing import Dict
 from typing import IO
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Set
-from typing import Tuple
 from typing import TYPE_CHECKING
-from typing import Union
 
 from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
 from _pytest._io.saferepr import saferepr
+from _pytest._io.saferepr import saferepr_unlimited
 from _pytest._version import version
 from _pytest.assertion import util
-from _pytest.assertion.util import (  # noqa: F401
-    format_explanation as _format_explanation,
-)
 from _pytest.config import Config
+from _pytest.fixtures import FixtureFunctionDefinition
 from _pytest.main import Session
 from _pytest.pathlib import absolutepath
 from _pytest.pathlib import fnmatch_ex
 from _pytest.stash import StashKey
 
+
+# fmt: off
+from _pytest.assertion.util import format_explanation as _format_explanation  # noqa:F401, isort:skip
+# fmt:on
+
 if TYPE_CHECKING:
     from _pytest.assertion import AssertionState
 
 
-assertstate_key = StashKey["AssertionState"]()
+class Sentinel:
+    pass
+
 
+assertstate_key = StashKey["AssertionState"]()
 
 # pytest caches rewritten pycs in pycache dirs
 PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}"
-PYC_EXT = ".py" + (__debug__ and "c" or "o")
+PYC_EXT = ".py" + ((__debug__ and "c") or "o")
 PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
 
+# Special marker that denotes we have just left a scope definition
+_SCOPE_END_MARKER = Sentinel()
+
 
 class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader):
     """PEP302/PEP451 import hook which rewrites asserts."""
@@ -63,17 +71,17 @@ def __init__(self, config: Config) -> None:
             self.fnpats = config.getini("python_files")
         except ValueError:
             self.fnpats = ["test_*.py", "*_test.py"]
-        self.session: Optional[Session] = None
-        self._rewritten_names: Dict[str, Path] = {}
-        self._must_rewrite: Set[str] = set()
+        self.session: Session | None = None
+        self._rewritten_names: dict[str, Path] = {}
+        self._must_rewrite: set[str] = set()
         # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
         # which might result in infinite recursion (#3506)
         self._writing_pyc = False
         self._basenames_to_check_rewrite = {"conftest"}
-        self._marked_for_rewrite_cache: Dict[str, bool] = {}
+        self._marked_for_rewrite_cache: dict[str, bool] = {}
         self._session_paths_checked = False
 
-    def set_session(self, session: Optional[Session]) -> None:
+    def set_session(self, session: Session | None) -> None:
         self.session = session
         self._session_paths_checked = False
 
@@ -83,26 +91,33 @@ def set_session(self, session: Optional[Session]) -> None:
     def find_spec(
         self,
         name: str,
-        path: Optional[Sequence[Union[str, bytes]]] = None,
-        target: Optional[types.ModuleType] = None,
-    ) -> Optional[importlib.machinery.ModuleSpec]:
+        path: Sequence[str | bytes] | None = None,
+        target: types.ModuleType | None = None,
+    ) -> importlib.machinery.ModuleSpec | None:
         if self._writing_pyc:
             return None
         state = self.config.stash[assertstate_key]
         if self._early_rewrite_bailout(name, state):
             return None
-        state.trace("find_module called for: %s" % name)
+        state.trace(f"find_module called for: {name}")
 
         # Type ignored because mypy is confused about the `self` binding here.
         spec = self._find_spec(name, path)  # type: ignore
+
+        if spec is None and path is not None:
+            # With --import-mode=importlib, PathFinder cannot find spec without modifying `sys.path`,
+            # causing inability to assert rewriting (#12659).
+            # At this point, try using the file path to find the module spec.
+            for _path_str in path:
+                spec = importlib.util.spec_from_file_location(name, _path_str)
+                if spec is not None:
+                    break
+
         if (
             # the import machinery could not find a file to import
             spec is None
             # this is a namespace package (without `__init__.py`)
             # there's nothing to rewrite there
-            # python3.6: `namespace`
-            # python3.7+: `None`
-            or spec.origin == "namespace"
             or spec.origin is None
             # we can only rewrite source files
             or not isinstance(spec.loader, importlib.machinery.SourceFileLoader)
@@ -125,7 +140,7 @@ def find_spec(
 
     def create_module(
         self, spec: importlib.machinery.ModuleSpec
-    ) -> Optional[types.ModuleType]:
+    ) -> types.ModuleType | None:
         return None  # default behaviour is fine
 
     def exec_module(self, module: types.ModuleType) -> None:
@@ -170,7 +185,7 @@ def exec_module(self, module: types.ModuleType) -> None:
             state.trace(f"found cached rewritten pyc for {fn}")
         exec(co, module.__dict__)
 
-    def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool:
+    def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool:
         """A fast way to get out of rewriting modules.
 
         Profiling has shown that the call to PathFinder.find_spec (inside of
@@ -183,7 +198,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool:
             for initial_path in self.session._initialpaths:
                 # Make something as c:/projects/my_project/path.py ->
                 #     ['c:', 'projects', 'my_project', 'path.py']
-                parts = str(initial_path).split(os.path.sep)
+                parts = str(initial_path).split(os.sep)
                 # add 'path' to basenames to be checked.
                 self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])
 
@@ -193,7 +208,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool:
             return False
 
         # For matching the name it must be as if it was a filename.
-        path = PurePath(os.path.sep.join(parts) + ".py")
+        path = PurePath(*parts).with_suffix(".py")
 
         for pat in self.fnpats:
             # if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based
@@ -209,7 +224,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool:
         state.trace(f"early skip of rewriting module: {name}")
         return True
 
-    def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool:
+    def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool:
         # always rewrite conftest files
         if os.path.basename(fn) == "conftest.py":
             state.trace(f"rewriting conftest file: {fn!r}")
@@ -230,7 +245,7 @@ def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool:
 
         return self._is_marked_for_rewrite(name, state)
 
-    def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool:
+    def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool:
         try:
             return self._marked_for_rewrite_cache[name]
         except KeyError:
@@ -266,23 +281,29 @@ def _warn_already_imported(self, name: str) -> None:
 
         self.config.issue_config_time_warning(
             PytestAssertRewriteWarning(
-                "Module already imported so cannot be rewritten: %s" % name
+                f"Module already imported so cannot be rewritten: {name}"
             ),
             stacklevel=5,
         )
 
-    def get_data(self, pathname: Union[str, bytes]) -> bytes:
+    def get_data(self, pathname: str | bytes) -> bytes:
         """Optional PEP302 get_data API."""
         with open(pathname, "rb") as f:
             return f.read()
 
-    if sys.version_info >= (3, 9):
+    if sys.version_info >= (3, 10):
+        if sys.version_info >= (3, 12):
+            from importlib.resources.abc import TraversableResources
+        else:
+            from importlib.abc import TraversableResources
 
-        def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources:  # type: ignore
-            from types import SimpleNamespace
-            from importlib.readers import FileReader
+        def get_resource_reader(self, name: str) -> TraversableResources:
+            if sys.version_info < (3, 11):
+                from importlib.readers import FileReader
+            else:
+                from importlib.resources.readers import FileReader
 
-            return FileReader(SimpleNamespace(path=self._rewritten_names[name]))
+            return FileReader(types.SimpleNamespace(path=self._rewritten_names[name]))
 
 
 def _write_pyc_fp(
@@ -293,9 +314,8 @@ def _write_pyc_fp(
     # import. However, there's little reason to deviate.
     fp.write(importlib.util.MAGIC_NUMBER)
     # https://www.python.org/dev/peps/pep-0552/
-    if sys.version_info >= (3, 7):
-        flags = b"\x00\x00\x00\x00"
-        fp.write(flags)
+    flags = b"\x00\x00\x00\x00"
+    fp.write(flags)
     # as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
     mtime = int(source_stat.st_mtime) & 0xFFFFFFFF
     size = source_stat.st_size & 0xFFFFFFFF
@@ -304,57 +324,32 @@ def _write_pyc_fp(
     fp.write(marshal.dumps(co))
 
 
-if sys.platform == "win32":
-    from atomicwrites import atomic_write
-
-    def _write_pyc(
-        state: "AssertionState",
-        co: types.CodeType,
-        source_stat: os.stat_result,
-        pyc: Path,
-    ) -> bool:
-        try:
-            with atomic_write(os.fspath(pyc), mode="wb", overwrite=True) as fp:
-                _write_pyc_fp(fp, source_stat, co)
-        except OSError as e:
-            state.trace(f"error writing pyc file at {pyc}: {e}")
-            # we ignore any failure to write the cache file
-            # there are many reasons, permission-denied, pycache dir being a
-            # file etc.
-            return False
-        return True
-
-
-else:
-
-    def _write_pyc(
-        state: "AssertionState",
-        co: types.CodeType,
-        source_stat: os.stat_result,
-        pyc: Path,
-    ) -> bool:
-        proc_pyc = f"{pyc}.{os.getpid()}"
-        try:
-            fp = open(proc_pyc, "wb")
-        except OSError as e:
-            state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}")
-            return False
-
-        try:
+def _write_pyc(
+    state: AssertionState,
+    co: types.CodeType,
+    source_stat: os.stat_result,
+    pyc: Path,
+) -> bool:
+    proc_pyc = f"{pyc}.{os.getpid()}"
+    try:
+        with open(proc_pyc, "wb") as fp:
             _write_pyc_fp(fp, source_stat, co)
-            os.rename(proc_pyc, pyc)
-        except OSError as e:
-            state.trace(f"error writing pyc file at {pyc}: {e}")
-            # we ignore any failure to write the cache file
-            # there are many reasons, permission-denied, pycache dir being a
-            # file etc.
-            return False
-        finally:
-            fp.close()
-        return True
+    except OSError as e:
+        state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}")
+        return False
+
+    try:
+        os.replace(proc_pyc, pyc)
+    except OSError as e:
+        state.trace(f"error writing pyc file at {pyc}: {e}")
+        # we ignore any failure to write the cache file
+        # there are many reasons, permission-denied, pycache dir being a
+        # file etc.
+        return False
+    return True
 
 
-def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]:
+def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]:
     """Read and rewrite *fn* and return the code object."""
     stat = os.stat(fn)
     source = fn.read_bytes()
@@ -367,7 +362,7 @@ def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeT
 
 def _read_pyc(
     source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None
-) -> Optional[types.CodeType]:
+) -> types.CodeType | None:
     """Possibly read a pytest pyc containing rewritten code.
 
     Return rewritten code if successful or None if not.
@@ -377,33 +372,31 @@ def _read_pyc(
     except OSError:
         return None
     with fp:
-        # https://www.python.org/dev/peps/pep-0552/
-        has_flags = sys.version_info >= (3, 7)
         try:
             stat_result = os.stat(source)
             mtime = int(stat_result.st_mtime)
             size = stat_result.st_size
-            data = fp.read(16 if has_flags else 12)
+            data = fp.read(16)
         except OSError as e:
             trace(f"_read_pyc({source}): OSError {e}")
             return None
         # Check for invalid or out of date pyc file.
-        if len(data) != (16 if has_flags else 12):
-            trace("_read_pyc(%s): invalid pyc (too short)" % source)
+        if len(data) != (16):
+            trace(f"_read_pyc({source}): invalid pyc (too short)")
             return None
         if data[:4] != importlib.util.MAGIC_NUMBER:
-            trace("_read_pyc(%s): invalid pyc (bad magic number)" % source)
+            trace(f"_read_pyc({source}): invalid pyc (bad magic number)")
             return None
-        if has_flags and data[4:8] != b"\x00\x00\x00\x00":
-            trace("_read_pyc(%s): invalid pyc (unsupported flags)" % source)
+        if data[4:8] != b"\x00\x00\x00\x00":
+            trace(f"_read_pyc({source}): invalid pyc (unsupported flags)")
             return None
-        mtime_data = data[8 if has_flags else 4 : 12 if has_flags else 8]
+        mtime_data = data[8:12]
         if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF:
-            trace("_read_pyc(%s): out of date" % source)
+            trace(f"_read_pyc({source}): out of date")
             return None
-        size_data = data[12 if has_flags else 8 : 16 if has_flags else 12]
+        size_data = data[12:16]
         if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF:
-            trace("_read_pyc(%s): invalid pyc (incorrect size)" % source)
+            trace(f"_read_pyc({source}): invalid pyc (incorrect size)")
             return None
         try:
             co = marshal.load(fp)
@@ -411,7 +404,7 @@ def _read_pyc(
             trace(f"_read_pyc({source}): marshal.load error {e}")
             return None
         if not isinstance(co, types.CodeType):
-            trace("_read_pyc(%s): not a code object" % source)
+            trace(f"_read_pyc({source}): not a code object")
             return None
         return co
 
@@ -419,8 +412,8 @@ def _read_pyc(
 def rewrite_asserts(
     mod: ast.Module,
     source: bytes,
-    module_path: Optional[str] = None,
-    config: Optional[Config] = None,
+    module_path: str | None = None,
+    config: Config | None = None,
 ) -> None:
     """Rewrite the assert statements in mod."""
     AssertionRewriter(module_path, config, source).run(mod)
@@ -436,13 +429,22 @@ def _saferepr(obj: object) -> str:
     sequences, especially '\n{' and '\n}' are likely to be present in
     JSON reprs.
     """
+    if isinstance(obj, types.MethodType):
+        # for bound methods, skip redundant <bound method ...> information
+        return obj.__name__
+
     maxsize = _get_maxsize_for_saferepr(util._config)
+    if not maxsize:
+        return saferepr_unlimited(obj).replace("\n", "\\n")
     return saferepr(obj, maxsize=maxsize).replace("\n", "\\n")
 
 
-def _get_maxsize_for_saferepr(config: Optional[Config]) -> Optional[int]:
+def _get_maxsize_for_saferepr(config: Config | None) -> int | None:
     """Get `maxsize` configuration for saferepr based on the given config object."""
-    verbosity = config.getoption("verbose") if config is not None else 0
+    if config is None:
+        verbosity = 0
+    else:
+        verbosity = config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
     if verbosity >= 2:
         return None
     if verbosity >= 1:
@@ -463,7 +465,7 @@ def _format_assertmsg(obj: object) -> str:
     # However in either case we want to preserve the newline.
     replaces = [("\n", "\n~"), ("%", "%%")]
     if not isinstance(obj, str):
-        obj = saferepr(obj)
+        obj = saferepr(obj, _get_maxsize_for_saferepr(util._config))
         replaces.append(("\\n", "\n~"))
 
     for r1, r2 in replaces:
@@ -474,7 +476,8 @@ def _format_assertmsg(obj: object) -> str:
 
 def _should_repr_global_name(obj: object) -> bool:
     if callable(obj):
-        return False
+        # For pytest fixtures the __repr__ method provides more information than the function name.
+        return isinstance(obj, FixtureFunctionDefinition)
 
     try:
         return not hasattr(obj, "__name__")
@@ -483,7 +486,7 @@ def _should_repr_global_name(obj: object) -> bool:
 
 
 def _format_boolop(explanations: Iterable[str], is_or: bool) -> str:
-    explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
+    explanation = "(" + ((is_or and " or ") or " and ").join(explanations) + ")"
     return explanation.replace("%", "%%")
 
 
@@ -514,7 +517,7 @@ def _call_assertion_pass(lineno: int, orig: str, expl: str) -> None:
 
 def _check_if_assertion_pass_impl() -> bool:
     """Check if any plugins implement the pytest_assertion_pass hook
-    in order not to generate explanation unecessarily (might be expensive)."""
+    in order not to generate explanation unnecessarily (might be expensive)."""
     return True if util._assertion_pass else False
 
 
@@ -555,14 +558,14 @@ def traverse_node(node: ast.AST) -> Iterator[ast.AST]:
 
 
 @functools.lru_cache(maxsize=1)
-def _get_assertion_exprs(src: bytes) -> Dict[int, str]:
+def _get_assertion_exprs(src: bytes) -> dict[int, str]:
     """Return a mapping from {lineno: "assertion test expression"}."""
-    ret: Dict[int, str] = {}
+    ret: dict[int, str] = {}
 
     depth = 0
-    lines: List[str] = []
-    assert_lineno: Optional[int] = None
-    seen_lines: Set[int] = set()
+    lines: list[str] = []
+    assert_lineno: int | None = None
+    seen_lines: set[int] = set()
 
     def _write_and_reset() -> None:
         nonlocal depth, lines, assert_lineno, seen_lines
@@ -596,7 +599,7 @@ def _write_and_reset() -> None:
                 # multi-line assert with message
                 elif lineno in seen_lines:
                     lines[-1] = lines[-1][:offset]
-                # multi line assert with escapd newline before message
+                # multi line assert with escaped newline before message
                 else:
                     lines.append(line[:offset])
                 _write_and_reset()
@@ -658,12 +661,18 @@ class AssertionRewriter(ast.NodeVisitor):
        .push_format_context() and .pop_format_context() which allows
        to build another %-formatted string while already building one.
 
-    This state is reset on every new assert statement visited and used
-    by the other visitors.
+    :scope: A tuple containing the current scope used for variables_overwrite.
+
+    :variables_overwrite: A dict filled with references to variables
+       that change value within an assert. This happens when a variable is
+       reassigned with the walrus operator
+
+    This state, except the variables_overwrite,  is reset on every new assert
+    statement visited and used by the other visitors.
     """
 
     def __init__(
-        self, module_path: Optional[str], config: Optional[Config], source: bytes
+        self, module_path: str | None, config: Config | None, source: bytes
     ) -> None:
         super().__init__()
         self.module_path = module_path
@@ -675,6 +684,10 @@ def __init__(
         else:
             self.enable_assertion_pass_hook = False
         self.source = source
+        self.scope: tuple[ast.AST, ...] = ()
+        self.variables_overwrite: defaultdict[tuple[ast.AST, ...], dict[str, str]] = (
+            defaultdict(dict)
+        )
 
     def run(self, mod: ast.Module) -> None:
         """Find all assert statements in *mod* and rewrite them."""
@@ -689,14 +702,15 @@ def run(self, mod: ast.Module) -> None:
         if doc is not None and self.is_rewrite_disabled(doc):
             return
         pos = 0
-        lineno = 1
+        item = None
         for item in mod.body:
             if (
                 expect_docstring
                 and isinstance(item, ast.Expr)
-                and isinstance(item.value, ast.Str)
+                and isinstance(item.value, ast.Constant)
+                and isinstance(item.value.value, str)
             ):
-                doc = item.value.s
+                doc = item.value.value
                 if self.is_rewrite_disabled(doc):
                     return
                 expect_docstring = False
@@ -737,12 +751,20 @@ def run(self, mod: ast.Module) -> None:
         mod.body[pos:pos] = imports
 
         # Collect asserts.
-        nodes: List[ast.AST] = [mod]
+        self.scope = (mod,)
+        nodes: list[ast.AST | Sentinel] = [mod]
         while nodes:
             node = nodes.pop()
+            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
+                self.scope = tuple((*self.scope, node))
+                nodes.append(_SCOPE_END_MARKER)
+            if node == _SCOPE_END_MARKER:
+                self.scope = self.scope[:-1]
+                continue
+            assert isinstance(node, ast.AST)
             for name, field in ast.iter_fields(node):
                 if isinstance(field, list):
-                    new: List[ast.AST] = []
+                    new: list[ast.AST] = []
                     for i, child in enumerate(field):
                         if isinstance(child, ast.Assert):
                             # Transform assert.
@@ -775,7 +797,7 @@ def assign(self, expr: ast.expr) -> ast.Name:
         """Give *expr* a name."""
         name = self.variable()
         self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr))
-        return ast.Name(name, ast.Load())
+        return ast.copy_location(ast.Name(name, ast.Load()), expr)
 
     def display(self, expr: ast.expr) -> ast.expr:
         """Call saferepr on the expression."""
@@ -814,7 +836,7 @@ def push_format_context(self) -> None:
         to format a string of %-formatted values as added by
         .explanation_param().
         """
-        self.explanation_specifiers: Dict[str, ast.expr] = {}
+        self.explanation_specifiers: dict[str, ast.expr] = {}
         self.stack.append(self.explanation_specifiers)
 
     def pop_format_context(self, expl_expr: ast.expr) -> ast.Name:
@@ -828,7 +850,7 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name:
         current = self.stack.pop()
         if self.stack:
             self.explanation_specifiers = self.stack[-1]
-        keys = [ast.Str(key) for key in current.keys()]
+        keys: list[ast.expr | None] = [ast.Constant(key) for key in current.keys()]
         format_dict = ast.Dict(keys, list(current.values()))
         form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
         name = "@py_format" + str(next(self.variable_counter))
@@ -837,13 +859,13 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name:
         self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form))
         return ast.Name(name, ast.Load())
 
-    def generic_visit(self, node: ast.AST) -> Tuple[ast.Name, str]:
+    def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]:
         """Handle expressions we don't have custom code for."""
         assert isinstance(node, ast.expr)
         res = self.assign(node)
         return res, self.explanation_param(self.display(res))
 
-    def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]:
+    def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]:
         """Return the AST statements to replace the ast.Assert instance.
 
         This rewrites the test of an assertion to provide
@@ -852,9 +874,10 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]:
         the expression is false.
         """
         if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1:
-            from _pytest.warning_types import PytestAssertRewriteWarning
             import warnings
 
+            from _pytest.warning_types import PytestAssertRewriteWarning
+
             # TODO: This assert should not be needed.
             assert self.module_path is not None
             warnings.warn_explicit(
@@ -866,15 +889,15 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]:
                 lineno=assert_.lineno,
             )
 
-        self.statements: List[ast.stmt] = []
-        self.variables: List[str] = []
+        self.statements: list[ast.stmt] = []
+        self.variables: list[str] = []
         self.variable_counter = itertools.count()
 
         if self.enable_assertion_pass_hook:
-            self.format_variables: List[str] = []
+            self.format_variables: list[str] = []
 
-        self.stack: List[Dict[str, ast.expr]] = []
-        self.expl_stmts: List[ast.stmt] = []
+        self.stack: list[dict[str, ast.expr]] = []
+        self.expl_stmts: list[ast.stmt] = []
         self.push_format_context()
         # Rewrite assert into a bunch of statements.
         top_condition, explanation = self.visit(assert_.test)
@@ -882,16 +905,16 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]:
         negation = ast.UnaryOp(ast.Not(), top_condition)
 
         if self.enable_assertion_pass_hook:  # Experimental pytest_assertion_pass hook
-            msg = self.pop_format_context(ast.Str(explanation))
+            msg = self.pop_format_context(ast.Constant(explanation))
 
             # Failed
             if assert_.msg:
                 assertmsg = self.helper("_format_assertmsg", assert_.msg)
                 gluestr = "\n>assert "
             else:
-                assertmsg = ast.Str("")
+                assertmsg = ast.Constant("")
                 gluestr = "assert "
-            err_explanation = ast.BinOp(ast.Str(gluestr), ast.Add(), msg)
+            err_explanation = ast.BinOp(ast.Constant(gluestr), ast.Add(), msg)
             err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation)
             err_name = ast.Name("AssertionError", ast.Load())
             fmt = self.helper("_format_explanation", err_msg)
@@ -907,27 +930,27 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]:
             hook_call_pass = ast.Expr(
                 self.helper(
                     "_call_assertion_pass",
-                    ast.Num(assert_.lineno),
-                    ast.Str(orig),
+                    ast.Constant(assert_.lineno),
+                    ast.Constant(orig),
                     fmt_pass,
                 )
             )
             # If any hooks implement assert_pass hook
             hook_impl_test = ast.If(
                 self.helper("_check_if_assertion_pass_impl"),
-                self.expl_stmts + [hook_call_pass],
+                [*self.expl_stmts, hook_call_pass],
                 [],
             )
-            statements_pass = [hook_impl_test]
+            statements_pass: list[ast.stmt] = [hook_impl_test]
 
             # Test for assertion condition
             main_test = ast.If(negation, statements_fail, statements_pass)
             self.statements.append(main_test)
             if self.format_variables:
-                variables = [
+                variables: list[ast.expr] = [
                     ast.Name(name, ast.Store()) for name in self.format_variables
                 ]
-                clear_format = ast.Assign(variables, ast.NameConstant(None))
+                clear_format = ast.Assign(variables, ast.Constant(None))
                 self.statements.append(clear_format)
 
         else:  # Original assertion rewriting
@@ -938,9 +961,9 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]:
                 assertmsg = self.helper("_format_assertmsg", assert_.msg)
                 explanation = "\n>assert " + explanation
             else:
-                assertmsg = ast.Str("")
+                assertmsg = ast.Constant("")
                 explanation = "assert " + explanation
-            template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation))
+            template = ast.BinOp(assertmsg, ast.Add(), ast.Constant(explanation))
             msg = self.pop_format_context(template)
             fmt = self.helper("_format_explanation", msg)
             err_name = ast.Name("AssertionError", ast.Load())
@@ -952,25 +975,40 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]:
         # Clear temporary variables by setting them to None.
         if self.variables:
             variables = [ast.Name(name, ast.Store()) for name in self.variables]
-            clear = ast.Assign(variables, ast.NameConstant(None))
+            clear = ast.Assign(variables, ast.Constant(None))
             self.statements.append(clear)
         # Fix locations (line numbers/column offsets).
         for stmt in self.statements:
             for node in traverse_node(stmt):
-                ast.copy_location(node, assert_)
+                if getattr(node, "lineno", None) is None:
+                    # apply the assertion location to all generated ast nodes without source location
+                    # and preserve the location of existing nodes or generated nodes with an correct location.
+                    ast.copy_location(node, assert_)
         return self.statements
 
-    def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]:
+    def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.NamedExpr, str]:
+        # This method handles the 'walrus operator' repr of the target
+        # name if it's a local variable or _should_repr_global_name()
+        # thinks it's acceptable.
+        locs = ast.Call(self.builtin("locals"), [], [])
+        target_id = name.target.id
+        inlocs = ast.Compare(ast.Constant(target_id), [ast.In()], [locs])
+        dorepr = self.helper("_should_repr_global_name", name)
+        test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
+        expr = ast.IfExp(test, self.display(name), ast.Constant(target_id))
+        return name, self.explanation_param(expr)
+
+    def visit_Name(self, name: ast.Name) -> tuple[ast.Name, str]:
         # Display the repr of the name if it's a local variable or
         # _should_repr_global_name() thinks it's acceptable.
         locs = ast.Call(self.builtin("locals"), [], [])
-        inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
+        inlocs = ast.Compare(ast.Constant(name.id), [ast.In()], [locs])
         dorepr = self.helper("_should_repr_global_name", name)
         test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
-        expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
+        expr = ast.IfExp(test, self.display(name), ast.Constant(name.id))
         return name, self.explanation_param(expr)
 
-    def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]:
+    def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]:
         res_var = self.variable()
         expl_list = self.assign(ast.List([], ast.Load()))
         app = ast.Attribute(expl_list, "append", ast.Load())
@@ -982,53 +1020,77 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]:
         # Process each operand, short-circuiting if needed.
         for i, v in enumerate(boolop.values):
             if i:
-                fail_inner: List[ast.stmt] = []
+                fail_inner: list[ast.stmt] = []
                 # cond is set in a prior loop iteration below
-                self.expl_stmts.append(ast.If(cond, fail_inner, []))  # noqa
+                self.expl_stmts.append(ast.If(cond, fail_inner, []))  # noqa: F821
                 self.expl_stmts = fail_inner
+                # Check if the left operand is a ast.NamedExpr and the value has already been visited
+                if (
+                    isinstance(v, ast.Compare)
+                    and isinstance(v.left, ast.NamedExpr)
+                    and v.left.target.id
+                    in [
+                        ast_expr.id
+                        for ast_expr in boolop.values[:i]
+                        if hasattr(ast_expr, "id")
+                    ]
+                ):
+                    pytest_temp = self.variable()
+                    self.variables_overwrite[self.scope][v.left.target.id] = v.left  # type:ignore[assignment]
+                    v.left.target.id = pytest_temp
             self.push_format_context()
             res, expl = self.visit(v)
             body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
-            expl_format = self.pop_format_context(ast.Str(expl))
+            expl_format = self.pop_format_context(ast.Constant(expl))
             call = ast.Call(app, [expl_format], [])
             self.expl_stmts.append(ast.Expr(call))
             if i < levels:
                 cond: ast.expr = res
                 if is_or:
                     cond = ast.UnaryOp(ast.Not(), cond)
-                inner: List[ast.stmt] = []
+                inner: list[ast.stmt] = []
                 self.statements.append(ast.If(cond, inner, []))
                 self.statements = body = inner
         self.statements = save
         self.expl_stmts = fail_save
-        expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or))
+        expl_template = self.helper("_format_boolop", expl_list, ast.Constant(is_or))
         expl = self.pop_format_context(expl_template)
         return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
 
-    def visit_UnaryOp(self, unary: ast.UnaryOp) -> Tuple[ast.Name, str]:
+    def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]:
         pattern = UNARY_MAP[unary.op.__class__]
         operand_res, operand_expl = self.visit(unary.operand)
-        res = self.assign(ast.UnaryOp(unary.op, operand_res))
+        res = self.assign(ast.copy_location(ast.UnaryOp(unary.op, operand_res), unary))
         return res, pattern % (operand_expl,)
 
-    def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]:
+    def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]:
         symbol = BINOP_MAP[binop.op.__class__]
         left_expr, left_expl = self.visit(binop.left)
         right_expr, right_expl = self.visit(binop.right)
         explanation = f"({left_expl} {symbol} {right_expl})"
-        res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
+        res = self.assign(
+            ast.copy_location(ast.BinOp(left_expr, binop.op, right_expr), binop)
+        )
         return res, explanation
 
-    def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]:
+    def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]:
         new_func, func_expl = self.visit(call.func)
         arg_expls = []
         new_args = []
         new_kwargs = []
         for arg in call.args:
+            if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get(
+                self.scope, {}
+            ):
+                arg = self.variables_overwrite[self.scope][arg.id]  # type:ignore[assignment]
             res, expl = self.visit(arg)
             arg_expls.append(expl)
             new_args.append(res)
         for keyword in call.keywords:
+            if isinstance(
+                keyword.value, ast.Name
+            ) and keyword.value.id in self.variables_overwrite.get(self.scope, {}):
+                keyword.value = self.variables_overwrite[self.scope][keyword.value.id]  # type:ignore[assignment]
             res, expl = self.visit(keyword.value)
             new_kwargs.append(ast.keyword(keyword.arg, res))
             if keyword.arg:
@@ -1037,50 +1099,66 @@ def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]:
                 arg_expls.append("**" + expl)
 
         expl = "{}({})".format(func_expl, ", ".join(arg_expls))
-        new_call = ast.Call(new_func, new_args, new_kwargs)
+        new_call = ast.copy_location(ast.Call(new_func, new_args, new_kwargs), call)
         res = self.assign(new_call)
         res_expl = self.explanation_param(self.display(res))
         outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}"
         return res, outer_expl
 
-    def visit_Starred(self, starred: ast.Starred) -> Tuple[ast.Starred, str]:
+    def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]:
         # A Starred node can appear in a function call.
         res, expl = self.visit(starred.value)
         new_starred = ast.Starred(res, starred.ctx)
         return new_starred, "*" + expl
 
-    def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]:
+    def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]:
         if not isinstance(attr.ctx, ast.Load):
             return self.generic_visit(attr)
         value, value_expl = self.visit(attr.value)
-        res = self.assign(ast.Attribute(value, attr.attr, ast.Load()))
+        res = self.assign(
+            ast.copy_location(ast.Attribute(value, attr.attr, ast.Load()), attr)
+        )
         res_expl = self.explanation_param(self.display(res))
         pat = "%s\n{%s = %s.%s\n}"
         expl = pat % (res_expl, res_expl, value_expl, attr.attr)
         return res, expl
 
-    def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]:
+    def visit_Compare(self, comp: ast.Compare) -> tuple[ast.expr, str]:
         self.push_format_context()
+        # We first check if we have overwritten a variable in the previous assert
+        if isinstance(
+            comp.left, ast.Name
+        ) and comp.left.id in self.variables_overwrite.get(self.scope, {}):
+            comp.left = self.variables_overwrite[self.scope][comp.left.id]  # type:ignore[assignment]
+        if isinstance(comp.left, ast.NamedExpr):
+            self.variables_overwrite[self.scope][comp.left.target.id] = comp.left  # type:ignore[assignment]
         left_res, left_expl = self.visit(comp.left)
         if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
             left_expl = f"({left_expl})"
         res_variables = [self.variable() for i in range(len(comp.ops))]
-        load_names = [ast.Name(v, ast.Load()) for v in res_variables]
+        load_names: list[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables]
         store_names = [ast.Name(v, ast.Store()) for v in res_variables]
         it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
-        expls = []
-        syms = []
+        expls: list[ast.expr] = []
+        syms: list[ast.expr] = []
         results = [left_res]
         for i, op, next_operand in it:
+            if (
+                isinstance(next_operand, ast.NamedExpr)
+                and isinstance(left_res, ast.Name)
+                and next_operand.target.id == left_res.id
+            ):
+                next_operand.target.id = self.variable()
+                self.variables_overwrite[self.scope][left_res.id] = next_operand  # type:ignore[assignment]
             next_res, next_expl = self.visit(next_operand)
             if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
                 next_expl = f"({next_expl})"
             results.append(next_res)
             sym = BINOP_MAP[op.__class__]
-            syms.append(ast.Str(sym))
+            syms.append(ast.Constant(sym))
             expl = f"{left_expl} {sym} {next_expl}"
-            expls.append(ast.Str(expl))
-            res_expr = ast.Compare(left_res, [op], [next_res])
+            expls.append(ast.Constant(expl))
+            res_expr = ast.copy_location(ast.Compare(left_res, [op], [next_res]), comp)
             self.statements.append(ast.Assign([store_names[i]], res_expr))
             left_res, left_expl = next_res, next_expl
         # Use pytest.assertion.util._reprcompare if that's available.
@@ -1095,6 +1173,7 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]:
             res: ast.expr = ast.BoolOp(ast.And(), load_names)
         else:
             res = load_names[0]
+
         return res, self.explanation_param(self.pop_format_context(expl_call))
 
 
@@ -1114,7 +1193,10 @@ def try_makedirs(cache_dir: Path) -> bool:
         return False
     except OSError as e:
         # as of now, EROFS doesn't have an equivalent OSError-subclass
-        if e.errno == errno.EROFS:
+        #
+        # squashfuse_ll returns ENOSYS "OSError: [Errno 38] Function not
+        # implemented" for a read-only error
+        if e.errno in {errno.EROFS, errno.ENOSYS}:
             return False
         raise
     return True
@@ -1122,7 +1204,7 @@ def try_makedirs(cache_dir: Path) -> bool:
 
 def get_cache_dir(file_path: Path) -> Path:
     """Return the cache directory to write .pyc files for the given .py file path."""
-    if sys.version_info >= (3, 8) and sys.pycache_prefix:
+    if sys.pycache_prefix:
         # given:
         #   prefix = '/tmp/pycs'
         #   path = '/home/user/proj/test_app.py'
diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py
index ce148dca095..4854a62ba6b 100644
--- a/src/_pytest/assertion/truncate.py
+++ b/src/_pytest/assertion/truncate.py
@@ -1,82 +1,125 @@
 """Utilities for truncating assertion output.
 
 Current default behaviour is to truncate assertion explanations at
-~8 terminal lines, unless running in "-vv" mode or running on CI.
+terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI.
 """
-from typing import List
-from typing import Optional
+
+from __future__ import annotations
 
 from _pytest.assertion import util
+from _pytest.config import Config
 from _pytest.nodes import Item
 
 
 DEFAULT_MAX_LINES = 8
-DEFAULT_MAX_CHARS = 8 * 80
+DEFAULT_MAX_CHARS = DEFAULT_MAX_LINES * 80
 USAGE_MSG = "use '-vv' to show"
 
 
-def truncate_if_required(
-    explanation: List[str], item: Item, max_length: Optional[int] = None
-) -> List[str]:
+def truncate_if_required(explanation: list[str], item: Item) -> list[str]:
     """Truncate this assertion explanation if the given test item is eligible."""
-    if _should_truncate_item(item):
-        return _truncate_explanation(explanation)
+    should_truncate, max_lines, max_chars = _get_truncation_parameters(item)
+    if should_truncate:
+        return _truncate_explanation(
+            explanation,
+            max_lines=max_lines,
+            max_chars=max_chars,
+        )
     return explanation
 
 
-def _should_truncate_item(item: Item) -> bool:
-    """Whether or not this test item is eligible for truncation."""
-    verbose = item.config.option.verbose
-    return verbose < 2 and not util.running_on_ci()
+def _get_truncation_parameters(item: Item) -> tuple[bool, int, int]:
+    """Return the truncation parameters related to the given item, as (should truncate, max lines, max chars)."""
+    # We do not need to truncate if one of conditions is met:
+    # 1. Verbosity level is 2 or more;
+    # 2. Test is being run in CI environment;
+    # 3. Both truncation_limit_lines and truncation_limit_chars
+    #    .ini parameters are set to 0 explicitly.
+    max_lines = item.config.getini("truncation_limit_lines")
+    max_lines = int(max_lines if max_lines is not None else DEFAULT_MAX_LINES)
+
+    max_chars = item.config.getini("truncation_limit_chars")
+    max_chars = int(max_chars if max_chars is not None else DEFAULT_MAX_CHARS)
+
+    verbose = item.config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
+
+    should_truncate = verbose < 2 and not util.running_on_ci()
+    should_truncate = should_truncate and (max_lines > 0 or max_chars > 0)
+
+    return should_truncate, max_lines, max_chars
 
 
 def _truncate_explanation(
-    input_lines: List[str],
-    max_lines: Optional[int] = None,
-    max_chars: Optional[int] = None,
-) -> List[str]:
+    input_lines: list[str],
+    max_lines: int,
+    max_chars: int,
+) -> list[str]:
     """Truncate given list of strings that makes up the assertion explanation.
 
-    Truncates to either 8 lines, or 640 characters - whichever the input reaches
-    first. The remaining lines will be replaced by a usage message.
+    Truncates to either max_lines, or max_chars - whichever the input reaches
+    first, taking the truncation explanation into account. The remaining lines
+    will be replaced by a usage message.
     """
-
-    if max_lines is None:
-        max_lines = DEFAULT_MAX_LINES
-    if max_chars is None:
-        max_chars = DEFAULT_MAX_CHARS
-
     # Check if truncation required
     input_char_count = len("".join(input_lines))
-    if len(input_lines) <= max_lines and input_char_count <= max_chars:
+    # The length of the truncation explanation depends on the number of lines
+    # removed but is at least 68 characters:
+    # The real value is
+    # 64 (for the base message:
+    # '...\n...Full output truncated (1 line hidden), use '-vv' to show")'
+    # )
+    # + 1 (for plural)
+    # + int(math.log10(len(input_lines) - max_lines)) (number of hidden line, at least 1)
+    # + 3 for the '...' added to the truncated line
+    # But if there's more than 100 lines it's very likely that we're going to
+    # truncate, so we don't need the exact value using log10.
+    tolerable_max_chars = (
+        max_chars + 70  # 64 + 1 (for plural) + 2 (for '99') + 3 for '...'
+    )
+    # The truncation explanation add two lines to the output
+    tolerable_max_lines = max_lines + 2
+    if (
+        len(input_lines) <= tolerable_max_lines
+        and input_char_count <= tolerable_max_chars
+    ):
         return input_lines
+    # Truncate first to max_lines, and then truncate to max_chars if necessary
+    if max_lines > 0:
+        truncated_explanation = input_lines[:max_lines]
+    else:
+        truncated_explanation = input_lines
+    truncated_char = True
+    # We reevaluate the need to truncate chars following removal of some lines
+    if len("".join(truncated_explanation)) > tolerable_max_chars and max_chars > 0:
+        truncated_explanation = _truncate_by_char_count(
+            truncated_explanation, max_chars
+        )
+    else:
+        truncated_char = False
 
-    # Truncate first to max_lines, and then truncate to max_chars if max_chars
-    # is exceeded.
-    truncated_explanation = input_lines[:max_lines]
-    truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars)
-
-    # Add ellipsis to final line
-    truncated_explanation[-1] = truncated_explanation[-1] + "..."
+    if truncated_explanation == input_lines:
+        # No truncation happened, so we do not need to add any explanations
+        return truncated_explanation
 
-    # Append useful message to explanation
     truncated_line_count = len(input_lines) - len(truncated_explanation)
-    truncated_line_count += 1  # Account for the part-truncated final line
-    msg = "...Full output truncated"
-    if truncated_line_count == 1:
-        msg += f" ({truncated_line_count} line hidden)"
+    if truncated_explanation[-1]:
+        # Add ellipsis and take into account part-truncated final line
+        truncated_explanation[-1] = truncated_explanation[-1] + "..."
+        if truncated_char:
+            # It's possible that we did not remove any char from this line
+            truncated_line_count += 1
     else:
-        msg += f" ({truncated_line_count} lines hidden)"
-    msg += f", {USAGE_MSG}"
-    truncated_explanation.extend(["", str(msg)])
-    return truncated_explanation
+        # Add proper ellipsis when we were able to fit a full line exactly
+        truncated_explanation[-1] = "..."
+    return [
+        *truncated_explanation,
+        "",
+        f"...Full output truncated ({truncated_line_count} line"
+        f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}",
+    ]
 
 
-def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]:
-    # Check if truncation required
-    if len("".join(input_lines)) <= max_chars:
-        return input_lines
-
+def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]:
     # Find point at which input length exceeds total allowed length
     iterated_char_count = 0
     for iterated_index, input_line in enumerate(input_lines):
diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py
index 19f1089c20a..c545e6cd20c 100644
--- a/src/_pytest/assertion/util.py
+++ b/src/_pytest/assertion/util.py
@@ -1,35 +1,54 @@
+# mypy: allow-untyped-defs
 """Utilities for assertion debugging."""
+
+from __future__ import annotations
+
 import collections.abc
+from collections.abc import Callable
+from collections.abc import Iterable
+from collections.abc import Mapping
+from collections.abc import Sequence
+from collections.abc import Set as AbstractSet
 import os
 import pprint
-from typing import AbstractSet
 from typing import Any
-from typing import Callable
-from typing import Iterable
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Sequence
+from typing import Literal
+from typing import Protocol
+from unicodedata import normalize
 
-import _pytest._code
 from _pytest import outcomes
-from _pytest._io.saferepr import _pformat_dispatch
-from _pytest._io.saferepr import safeformat
+import _pytest._code
+from _pytest._io.pprint import PrettyPrinter
 from _pytest._io.saferepr import saferepr
+from _pytest._io.saferepr import saferepr_unlimited
 from _pytest.config import Config
 
+
 # The _reprcompare attribute on the util module is used by the new assertion
 # interpretation code and assertion rewriter to detect this plugin was
 # loaded and in turn call the hooks defined here as part of the
 # DebugInterpreter.
-_reprcompare: Optional[Callable[[str, object, object], Optional[str]]] = None
+_reprcompare: Callable[[str, object, object], str | None] | None = None
 
 # Works similarly as _reprcompare attribute. Is populated with the hook call
 # when pytest_runtest_setup is called.
-_assertion_pass: Optional[Callable[[int, str, str], None]] = None
+_assertion_pass: Callable[[int, str, str], None] | None = None
 
 # Config object which is assigned during pytest_runtest_protocol.
-_config: Optional[Config] = None
+_config: Config | None = None
+
+
+class _HighlightFunc(Protocol):
+    def __call__(self, source: str, lexer: Literal["diff", "python"] = "python") -> str:
+        """Apply highlighting to the given source."""
+
+
+def dummy_highlighter(source: str, lexer: Literal["diff", "python"] = "python") -> str:
+    """Dummy highlighter that returns the text unprocessed.
+
+    Needed for _notin_text, as the diff gets post-processed to only show the "+" part.
+    """
+    return source
 
 
 def format_explanation(explanation: str) -> str:
@@ -47,7 +66,7 @@ def format_explanation(explanation: str) -> str:
     return "\n".join(result)
 
 
-def _split_explanation(explanation: str) -> List[str]:
+def _split_explanation(explanation: str) -> list[str]:
     r"""Return a list of individual lines in the explanation.
 
     This will return a list of lines split on '\n{', '\n}' and '\n~'.
@@ -64,7 +83,7 @@ def _split_explanation(explanation: str) -> List[str]:
     return lines
 
 
-def _format_lines(lines: Sequence[str]) -> List[str]:
+def _format_lines(lines: Sequence[str]) -> list[str]:
     """Format the individual lines.
 
     This will replace the '{', '}' and '~' characters of our mini formatting
@@ -131,54 +150,107 @@ def isiterable(obj: Any) -> bool:
     try:
         iter(obj)
         return not istext(obj)
-    except TypeError:
+    except Exception:
         return False
 
 
-def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]:
+def has_default_eq(
+    obj: object,
+) -> bool:
+    """Check if an instance of an object contains the default eq
+
+    First, we check if the object's __eq__ attribute has __code__,
+    if so, we check the equally of the method code filename (__code__.co_filename)
+    to the default one generated by the dataclass and attr module
+    for dataclasses the default co_filename is <string>, for attrs class, the __eq__ should contain "attrs eq generated"
+    """
+    # inspired from https://github.com/willmcgugan/rich/blob/07d51ffc1aee6f16bd2e5a25b4e82850fb9ed778/rich/pretty.py#L68
+    if hasattr(obj.__eq__, "__code__") and hasattr(obj.__eq__.__code__, "co_filename"):
+        code_filename = obj.__eq__.__code__.co_filename
+
+        if isattrs(obj):
+            return "attrs generated " in code_filename
+
+        return code_filename == "<string>"  # data class
+    return True
+
+
+def assertrepr_compare(
+    config, op: str, left: Any, right: Any, use_ascii: bool = False
+) -> list[str] | None:
     """Return specialised explanations for some operators/operands."""
-    verbose = config.getoption("verbose")
+    verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS)
+
+    # Strings which normalize equal are often hard to distinguish when printed; use ascii() to make this easier.
+    # See issue #3246.
+    use_ascii = (
+        isinstance(left, str)
+        and isinstance(right, str)
+        and normalize("NFD", left) == normalize("NFD", right)
+    )
+
     if verbose > 1:
-        left_repr = safeformat(left)
-        right_repr = safeformat(right)
+        left_repr = saferepr_unlimited(left, use_ascii=use_ascii)
+        right_repr = saferepr_unlimited(right, use_ascii=use_ascii)
     else:
         # XXX: "15 chars indentation" is wrong
         #      ("E       AssertionError: assert "); should use term width.
         maxsize = (
             80 - 15 - len(op) - 2
         ) // 2  # 15 chars indentation, 1 space around op
-        left_repr = saferepr(left, maxsize=maxsize)
-        right_repr = saferepr(right, maxsize=maxsize)
+
+        left_repr = saferepr(left, maxsize=maxsize, use_ascii=use_ascii)
+        right_repr = saferepr(right, maxsize=maxsize, use_ascii=use_ascii)
 
     summary = f"{left_repr} {op} {right_repr}"
+    highlighter = config.get_terminal_writer()._highlight
 
     explanation = None
     try:
         if op == "==":
-            explanation = _compare_eq_any(left, right, verbose)
+            explanation = _compare_eq_any(left, right, highlighter, verbose)
         elif op == "not in":
             if istext(left) and istext(right):
                 explanation = _notin_text(left, right, verbose)
+        elif op == "!=":
+            if isset(left) and isset(right):
+                explanation = ["Both sets are equal"]
+        elif op == ">=":
+            if isset(left) and isset(right):
+                explanation = _compare_gte_set(left, right, highlighter, verbose)
+        elif op == "<=":
+            if isset(left) and isset(right):
+                explanation = _compare_lte_set(left, right, highlighter, verbose)
+        elif op == ">":
+            if isset(left) and isset(right):
+                explanation = _compare_gt_set(left, right, highlighter, verbose)
+        elif op == "<":
+            if isset(left) and isset(right):
+                explanation = _compare_lt_set(left, right, highlighter, verbose)
+
     except outcomes.Exit:
         raise
     except Exception:
+        repr_crash = _pytest._code.ExceptionInfo.from_current()._getreprcrash()
         explanation = [
-            "(pytest_assertion plugin: representation of details failed: {}.".format(
-                _pytest._code.ExceptionInfo.from_current()._getreprcrash()
-            ),
+            f"(pytest_assertion plugin: representation of details failed: {repr_crash}.",
             " Probably an object has a faulty __repr__.)",
         ]
 
     if not explanation:
         return None
 
-    return [summary] + explanation
+    if explanation[0] != "":
+        explanation = ["", *explanation]
+    return [summary, *explanation]
 
 
-def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
+def _compare_eq_any(
+    left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0
+) -> list[str]:
     explanation = []
     if istext(left) and istext(right):
-        explanation = _diff_text(left, right, verbose)
+        explanation = _diff_text(left, right, highlighter, verbose)
     else:
         from _pytest.python_api import ApproxBase
 
@@ -188,31 +260,31 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
             other_side = right if isinstance(left, ApproxBase) else left
 
             explanation = approx_side._repr_compare(other_side)
-        elif type(left) == type(right) and (
+        elif type(left) is type(right) and (
             isdatacls(left) or isattrs(left) or isnamedtuple(left)
         ):
             # Note: unlike dataclasses/attrs, namedtuples compare only the
             # field values, not the type or field names. But this branch
             # intentionally only handles the same-type case, which was often
             # used in older code bases before dataclasses/attrs were available.
-            explanation = _compare_eq_cls(left, right, verbose)
+            explanation = _compare_eq_cls(left, right, highlighter, verbose)
         elif issequence(left) and issequence(right):
-            explanation = _compare_eq_sequence(left, right, verbose)
+            explanation = _compare_eq_sequence(left, right, highlighter, verbose)
         elif isset(left) and isset(right):
-            explanation = _compare_eq_set(left, right, verbose)
+            explanation = _compare_eq_set(left, right, highlighter, verbose)
         elif isdict(left) and isdict(right):
-            explanation = _compare_eq_dict(left, right, verbose)
-        elif verbose > 0:
-            explanation = _compare_eq_verbose(left, right)
+            explanation = _compare_eq_dict(left, right, highlighter, verbose)
 
         if isiterable(left) and isiterable(right):
-            expl = _compare_eq_iterable(left, right, verbose)
+            expl = _compare_eq_iterable(left, right, highlighter, verbose)
             explanation.extend(expl)
 
     return explanation
 
 
-def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
+def _diff_text(
+    left: str, right: str, highlighter: _HighlightFunc, verbose: int = 0
+) -> list[str]:
     """Return the explanation for the diff between text.
 
     Unless --verbose is used this will skip leading and trailing
@@ -220,7 +292,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
     """
     from difflib import ndiff
 
-    explanation: List[str] = []
+    explanation: list[str] = []
 
     if verbose < 1:
         i = 0  # just in case left or right has zero length
@@ -230,7 +302,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
         if i > 42:
             i -= 10  # Provide some context
             explanation = [
-                "Skipping %s identical leading characters in diff, use -v to show" % i
+                f"Skipping {i} identical leading characters in diff, use -v to show"
             ]
             left = left[i:]
             right = right[i:]
@@ -241,8 +313,8 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
             if i > 42:
                 i -= 10  # Provide some context
                 explanation += [
-                    "Skipping {} identical trailing "
-                    "characters in diff, use -v to show".format(i)
+                    f"Skipping {i} identical trailing "
+                    "characters in diff, use -v to show"
                 ]
                 left = left[:-i]
                 right = right[:-i]
@@ -253,73 +325,55 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
         explanation += ["Strings contain only whitespace, escaping them using repr()"]
     # "right" is the expected base against which we compare "left",
     # see https://github.com/pytest-dev/pytest/issues/3333
-    explanation += [
-        line.strip("\n")
-        for line in ndiff(right.splitlines(keepends), left.splitlines(keepends))
-    ]
-    return explanation
-
-
-def _compare_eq_verbose(left: Any, right: Any) -> List[str]:
-    keepends = True
-    left_lines = repr(left).splitlines(keepends)
-    right_lines = repr(right).splitlines(keepends)
-
-    explanation: List[str] = []
-    explanation += ["+" + line for line in left_lines]
-    explanation += ["-" + line for line in right_lines]
-
+    explanation.extend(
+        highlighter(
+            "\n".join(
+                line.strip("\n")
+                for line in ndiff(right.splitlines(keepends), left.splitlines(keepends))
+            ),
+            lexer="diff",
+        ).splitlines()
+    )
     return explanation
 
 
-def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
-    """Move opening/closing parenthesis/bracket to own lines."""
-    opening = lines[0][:1]
-    if opening in ["(", "[", "{"]:
-        lines[0] = " " + lines[0][1:]
-        lines[:] = [opening] + lines
-    closing = lines[-1][-1:]
-    if closing in [")", "]", "}"]:
-        lines[-1] = lines[-1][:-1] + ","
-        lines[:] = lines + [closing]
-
-
 def _compare_eq_iterable(
-    left: Iterable[Any], right: Iterable[Any], verbose: int = 0
-) -> List[str]:
-    if not verbose and not running_on_ci():
-        return ["Use -v to get the full diff"]
+    left: Iterable[Any],
+    right: Iterable[Any],
+    highlighter: _HighlightFunc,
+    verbose: int = 0,
+) -> list[str]:
+    if verbose <= 0 and not running_on_ci():
+        return ["Use -v to get more diff"]
     # dynamic import to speedup pytest
     import difflib
 
-    left_formatting = pprint.pformat(left).splitlines()
-    right_formatting = pprint.pformat(right).splitlines()
+    left_formatting = PrettyPrinter().pformat(left).splitlines()
+    right_formatting = PrettyPrinter().pformat(right).splitlines()
 
-    # Re-format for different output lengths.
-    lines_left = len(left_formatting)
-    lines_right = len(right_formatting)
-    if lines_left != lines_right:
-        left_formatting = _pformat_dispatch(left).splitlines()
-        right_formatting = _pformat_dispatch(right).splitlines()
-
-    if lines_left > 1 or lines_right > 1:
-        _surrounding_parens_on_own_lines(left_formatting)
-        _surrounding_parens_on_own_lines(right_formatting)
-
-    explanation = ["Full diff:"]
+    explanation = ["", "Full diff:"]
     # "right" is the expected base against which we compare "left",
     # see https://github.com/pytest-dev/pytest/issues/3333
     explanation.extend(
-        line.rstrip() for line in difflib.ndiff(right_formatting, left_formatting)
+        highlighter(
+            "\n".join(
+                line.rstrip()
+                for line in difflib.ndiff(right_formatting, left_formatting)
+            ),
+            lexer="diff",
+        ).splitlines()
     )
     return explanation
 
 
 def _compare_eq_sequence(
-    left: Sequence[Any], right: Sequence[Any], verbose: int = 0
-) -> List[str]:
+    left: Sequence[Any],
+    right: Sequence[Any],
+    highlighter: _HighlightFunc,
+    verbose: int = 0,
+) -> list[str]:
     comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
-    explanation: List[str] = []
+    explanation: list[str] = []
     len_left = len(left)
     len_right = len(right)
     for i in range(min(len_left, len_right)):
@@ -339,7 +393,10 @@ def _compare_eq_sequence(
                 left_value = left[i]
                 right_value = right[i]
 
-            explanation += [f"At index {i} diff: {left_value!r} != {right_value!r}"]
+            explanation.append(
+                f"At index {i} diff:"
+                f" {highlighter(repr(left_value))} != {highlighter(repr(right_value))}"
+            )
             break
 
     if comparing_bytes:
@@ -359,77 +416,141 @@ def _compare_eq_sequence(
             extra = saferepr(right[len_left])
 
         if len_diff == 1:
-            explanation += [f"{dir_with_more} contains one more item: {extra}"]
+            explanation += [
+                f"{dir_with_more} contains one more item: {highlighter(extra)}"
+            ]
         else:
             explanation += [
-                "%s contains %d more items, first extra item: %s"
-                % (dir_with_more, len_diff, extra)
+                f"{dir_with_more} contains {len_diff} more items, first extra item: {highlighter(extra)}"
             ]
     return explanation
 
 
 def _compare_eq_set(
-    left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0
-) -> List[str]:
+    left: AbstractSet[Any],
+    right: AbstractSet[Any],
+    highlighter: _HighlightFunc,
+    verbose: int = 0,
+) -> list[str]:
     explanation = []
-    diff_left = left - right
-    diff_right = right - left
-    if diff_left:
-        explanation.append("Extra items in the left set:")
-        for item in diff_left:
-            explanation.append(saferepr(item))
-    if diff_right:
-        explanation.append("Extra items in the right set:")
-        for item in diff_right:
-            explanation.append(saferepr(item))
+    explanation.extend(_set_one_sided_diff("left", left, right, highlighter))
+    explanation.extend(_set_one_sided_diff("right", right, left, highlighter))
+    return explanation
+
+
+def _compare_gt_set(
+    left: AbstractSet[Any],
+    right: AbstractSet[Any],
+    highlighter: _HighlightFunc,
+    verbose: int = 0,
+) -> list[str]:
+    explanation = _compare_gte_set(left, right, highlighter)
+    if not explanation:
+        return ["Both sets are equal"]
+    return explanation
+
+
+def _compare_lt_set(
+    left: AbstractSet[Any],
+    right: AbstractSet[Any],
+    highlighter: _HighlightFunc,
+    verbose: int = 0,
+) -> list[str]:
+    explanation = _compare_lte_set(left, right, highlighter)
+    if not explanation:
+        return ["Both sets are equal"]
+    return explanation
+
+
+def _compare_gte_set(
+    left: AbstractSet[Any],
+    right: AbstractSet[Any],
+    highlighter: _HighlightFunc,
+    verbose: int = 0,
+) -> list[str]:
+    return _set_one_sided_diff("right", right, left, highlighter)
+
+
+def _compare_lte_set(
+    left: AbstractSet[Any],
+    right: AbstractSet[Any],
+    highlighter: _HighlightFunc,
+    verbose: int = 0,
+) -> list[str]:
+    return _set_one_sided_diff("left", left, right, highlighter)
+
+
+def _set_one_sided_diff(
+    posn: str,
+    set1: AbstractSet[Any],
+    set2: AbstractSet[Any],
+    highlighter: _HighlightFunc,
+) -> list[str]:
+    explanation = []
+    diff = set1 - set2
+    if diff:
+        explanation.append(f"Extra items in the {posn} set:")
+        for item in diff:
+            explanation.append(highlighter(saferepr(item)))
     return explanation
 
 
 def _compare_eq_dict(
-    left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0
-) -> List[str]:
-    explanation: List[str] = []
+    left: Mapping[Any, Any],
+    right: Mapping[Any, Any],
+    highlighter: _HighlightFunc,
+    verbose: int = 0,
+) -> list[str]:
+    explanation: list[str] = []
     set_left = set(left)
     set_right = set(right)
     common = set_left.intersection(set_right)
     same = {k: left[k] for k in common if left[k] == right[k]}
     if same and verbose < 2:
-        explanation += ["Omitting %s identical items, use -vv to show" % len(same)]
+        explanation += [f"Omitting {len(same)} identical items, use -vv to show"]
     elif same:
         explanation += ["Common items:"]
-        explanation += pprint.pformat(same).splitlines()
+        explanation += highlighter(pprint.pformat(same)).splitlines()
     diff = {k for k in common if left[k] != right[k]}
     if diff:
         explanation += ["Differing items:"]
         for k in diff:
-            explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
+            explanation += [
+                highlighter(saferepr({k: left[k]}))
+                + " != "
+                + highlighter(saferepr({k: right[k]}))
+            ]
     extra_left = set_left - set_right
     len_extra_left = len(extra_left)
     if len_extra_left:
         explanation.append(
-            "Left contains %d more item%s:"
-            % (len_extra_left, "" if len_extra_left == 1 else "s")
+            f"Left contains {len_extra_left} more item{'' if len_extra_left == 1 else 's'}:"
         )
         explanation.extend(
-            pprint.pformat({k: left[k] for k in extra_left}).splitlines()
+            highlighter(pprint.pformat({k: left[k] for k in extra_left})).splitlines()
         )
     extra_right = set_right - set_left
     len_extra_right = len(extra_right)
     if len_extra_right:
         explanation.append(
-            "Right contains %d more item%s:"
-            % (len_extra_right, "" if len_extra_right == 1 else "s")
+            f"Right contains {len_extra_right} more item{'' if len_extra_right == 1 else 's'}:"
         )
         explanation.extend(
-            pprint.pformat({k: right[k] for k in extra_right}).splitlines()
+            highlighter(pprint.pformat({k: right[k] for k in extra_right})).splitlines()
         )
     return explanation
 
 
-def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
+def _compare_eq_cls(
+    left: Any, right: Any, highlighter: _HighlightFunc, verbose: int
+) -> list[str]:
+    if not has_default_eq(left):
+        return []
     if isdatacls(left):
-        all_fields = left.__dataclass_fields__
-        fields_to_check = [field for field, info in all_fields.items() if info.compare]
+        import dataclasses
+
+        all_fields = dataclasses.fields(left)
+        fields_to_check = [info.name for info in all_fields if info.compare]
     elif isattrs(left):
         all_fields = left.__attrs_attrs__
         fields_to_check = [field.name for field in all_fields if getattr(field, "eq")]
@@ -451,35 +572,37 @@ def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
     if same or diff:
         explanation += [""]
     if same and verbose < 2:
-        explanation.append("Omitting %s identical items, use -vv to show" % len(same))
+        explanation.append(f"Omitting {len(same)} identical items, use -vv to show")
     elif same:
         explanation += ["Matching attributes:"]
-        explanation += pprint.pformat(same).splitlines()
+        explanation += highlighter(pprint.pformat(same)).splitlines()
     if diff:
         explanation += ["Differing attributes:"]
-        explanation += pprint.pformat(diff).splitlines()
+        explanation += highlighter(pprint.pformat(diff)).splitlines()
         for field in diff:
             field_left = getattr(left, field)
             field_right = getattr(right, field)
             explanation += [
                 "",
-                "Drill down into differing attribute %s:" % field,
-                ("%s%s: %r != %r") % (indent, field, field_left, field_right),
+                f"Drill down into differing attribute {field}:",
+                f"{indent}{field}: {highlighter(repr(field_left))} != {highlighter(repr(field_right))}",
             ]
             explanation += [
                 indent + line
-                for line in _compare_eq_any(field_left, field_right, verbose)
+                for line in _compare_eq_any(
+                    field_left, field_right, highlighter, verbose
+                )
             ]
     return explanation
 
 
-def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]:
+def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]:
     index = text.find(term)
     head = text[:index]
     tail = text[index + len(term) :]
     correct_text = head + tail
-    diff = _diff_text(text, correct_text, verbose)
-    newdiff = ["%s is contained here:" % saferepr(term, maxsize=42)]
+    diff = _diff_text(text, correct_text, dummy_highlighter, verbose)
+    newdiff = [f"{saferepr(term, maxsize=42)} is contained here:"]
     for line in diff:
         if line.startswith("Skipping"):
             continue
diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py
index 681d02b4093..dea60109b51 100755
--- a/src/_pytest/cacheprovider.py
+++ b/src/_pytest/cacheprovider.py
@@ -1,25 +1,25 @@
+# mypy: allow-untyped-defs
 """Implementation of the cache provider."""
+
 # This plugin was not named "cache" to avoid conflicts with the external
 # pytest-cache version.
+from __future__ import annotations
+
+from collections.abc import Generator
+from collections.abc import Iterable
+import dataclasses
+import errno
 import json
 import os
 from pathlib import Path
-from typing import Dict
-from typing import Generator
-from typing import Iterable
-from typing import List
-from typing import Optional
-from typing import Set
-from typing import Union
-
-import attr
+import tempfile
+from typing import final
 
 from .pathlib import resolve_from_str
 from .pathlib import rm_rf
 from .reports import CollectReport
 from _pytest import nodes
 from _pytest._io import TerminalWriter
-from _pytest.compat import final
 from _pytest.config import Config
 from _pytest.config import ExitCode
 from _pytest.config import hookimpl
@@ -28,8 +28,8 @@
 from _pytest.fixtures import fixture
 from _pytest.fixtures import FixtureRequest
 from _pytest.main import Session
-from _pytest.python import Module
-from _pytest.python import Package
+from _pytest.nodes import Directory
+from _pytest.nodes import File
 from _pytest.reports import TestReport
 
 
@@ -53,10 +53,12 @@
 
 
 @final
-@attr.s(init=False, auto_attribs=True)
+@dataclasses.dataclass
 class Cache:
-    _cachedir: Path = attr.ib(repr=False)
-    _config: Config = attr.ib(repr=False)
+    """Instance of the `cache` fixture."""
+
+    _cachedir: Path = dataclasses.field(repr=False)
+    _config: Config = dataclasses.field(repr=False)
 
     # Sub-directory under cache-dir for directories created by `mkdir()`.
     _CACHE_PREFIX_DIRS = "d"
@@ -72,7 +74,7 @@ def __init__(
         self._config = config
 
     @classmethod
-    def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache":
+    def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache:
         """Create the Cache instance for a Config.
 
         :meta private:
@@ -111,6 +113,7 @@ def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None:
         """
         check_ispytest(_ispytest)
         import warnings
+
         from _pytest.warning_types import PytestCacheWarning
 
         warnings.warn(
@@ -119,6 +122,10 @@ def warn(self, fmt: str, *, _ispytest: bool = False, **args: object) -> None:
             stacklevel=3,
         )
 
+    def _mkdir(self, path: Path) -> None:
+        self._ensure_cache_dir_and_supporting_files()
+        path.mkdir(exist_ok=True, parents=True)
+
     def mkdir(self, name: str) -> Path:
         """Return a directory path object with the given name.
 
@@ -137,7 +144,7 @@ def mkdir(self, name: str) -> Path:
         if len(path.parts) > 1:
             raise ValueError("name is not allowed to contain path separators")
         res = self._cachedir.joinpath(self._CACHE_PREFIX_DIRS, path)
-        res.mkdir(exist_ok=True, parents=True)
+        self._mkdir(res)
         return res
 
     def _getvaluepath(self, key: str) -> Path:
@@ -157,7 +164,7 @@ def get(self, key: str, default):
         """
         path = self._getvaluepath(key)
         try:
-            with path.open("r") as f:
+            with path.open("r", encoding="UTF-8") as f:
                 return json.load(f)
         except (ValueError, OSError):
             return default
@@ -174,71 +181,104 @@ def set(self, key: str, value: object) -> None:
         """
         path = self._getvaluepath(key)
         try:
-            if path.parent.is_dir():
-                cache_dir_exists_already = True
-            else:
-                cache_dir_exists_already = self._cachedir.exists()
-                path.parent.mkdir(exist_ok=True, parents=True)
-        except OSError:
-            self.warn("could not create cache path {path}", path=path, _ispytest=True)
+            self._mkdir(path.parent)
+        except OSError as exc:
+            self.warn(
+                f"could not create cache path {path}: {exc}",
+                _ispytest=True,
+            )
             return
-        if not cache_dir_exists_already:
-            self._ensure_supporting_files()
-        data = json.dumps(value, indent=2)
+        data = json.dumps(value, ensure_ascii=False, indent=2)
         try:
-            f = path.open("w")
-        except OSError:
-            self.warn("cache could not write path {path}", path=path, _ispytest=True)
+            f = path.open("w", encoding="UTF-8")
+        except OSError as exc:
+            self.warn(
+                f"cache could not write path {path}: {exc}",
+                _ispytest=True,
+            )
         else:
             with f:
                 f.write(data)
 
-    def _ensure_supporting_files(self) -> None:
-        """Create supporting files in the cache dir that are not really part of the cache."""
-        readme_path = self._cachedir / "README.md"
-        readme_path.write_text(README_CONTENT)
-
-        gitignore_path = self._cachedir.joinpath(".gitignore")
-        msg = "# Created by pytest automatically.\n*\n"
-        gitignore_path.write_text(msg, encoding="UTF-8")
+    def _ensure_cache_dir_and_supporting_files(self) -> None:
+        """Create the cache dir and its supporting files."""
+        if self._cachedir.is_dir():
+            return
 
-        cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
-        cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
+        self._cachedir.parent.mkdir(parents=True, exist_ok=True)
+        with tempfile.TemporaryDirectory(
+            prefix="pytest-cache-files-",
+            dir=self._cachedir.parent,
+        ) as newpath:
+            path = Path(newpath)
+
+            # Reset permissions to the default, see #12308.
+            # Note: there's no way to get the current umask atomically, eek.
+            umask = os.umask(0o022)
+            os.umask(umask)
+            path.chmod(0o777 - umask)
+
+            with open(path.joinpath("README.md"), "x", encoding="UTF-8") as f:
+                f.write(README_CONTENT)
+            with open(path.joinpath(".gitignore"), "x", encoding="UTF-8") as f:
+                f.write("# Created by pytest automatically.\n*\n")
+            with open(path.joinpath("CACHEDIR.TAG"), "xb") as f:
+                f.write(CACHEDIR_TAG_CONTENT)
+
+            try:
+                path.rename(self._cachedir)
+            except OSError as e:
+                # If 2 concurrent pytests both race to the rename, the loser
+                # gets "Directory not empty" from the rename. In this case,
+                # everything is handled so just continue (while letting the
+                # temporary directory be cleaned up).
+                # On Windows, the error is a FileExistsError which translates to EEXIST.
+                if e.errno not in (errno.ENOTEMPTY, errno.EEXIST):
+                    raise
+            else:
+                # Create a directory in place of the one we just moved so that
+                # `TemporaryDirectory`'s cleanup doesn't complain.
+                #
+                # TODO: pass ignore_cleanup_errors=True when we no longer support python < 3.10.
+                # See https://github.com/python/cpython/issues/74168. Note that passing
+                # delete=False would do the wrong thing in case of errors and isn't supported
+                # until python 3.12.
+                path.mkdir()
 
 
 class LFPluginCollWrapper:
-    def __init__(self, lfplugin: "LFPlugin") -> None:
+    def __init__(self, lfplugin: LFPlugin) -> None:
         self.lfplugin = lfplugin
         self._collected_at_least_one_failure = False
 
-    @hookimpl(hookwrapper=True)
-    def pytest_make_collect_report(self, collector: nodes.Collector):
-        if isinstance(collector, Session):
-            out = yield
-            res: CollectReport = out.get_result()
-
+    @hookimpl(wrapper=True)
+    def pytest_make_collect_report(
+        self, collector: nodes.Collector
+    ) -> Generator[None, CollectReport, CollectReport]:
+        res = yield
+        if isinstance(collector, (Session, Directory)):
             # Sort any lf-paths to the beginning.
             lf_paths = self.lfplugin._last_failed_paths
 
+            # Use stable sort to prioritize last failed.
+            def sort_key(node: nodes.Item | nodes.Collector) -> bool:
+                return node.path in lf_paths
+
             res.result = sorted(
                 res.result,
-                # use stable sort to priorize last failed
-                key=lambda x: x.path in lf_paths,
+                key=sort_key,
                 reverse=True,
             )
-            return
 
-        elif isinstance(collector, Module):
+        elif isinstance(collector, File):
             if collector.path in self.lfplugin._last_failed_paths:
-                out = yield
-                res = out.get_result()
                 result = res.result
                 lastfailed = self.lfplugin.lastfailed
 
                 # Only filter with known failures.
                 if not self._collected_at_least_one_failure:
                     if not any(x.nodeid in lastfailed for x in result):
-                        return
+                        return res
                     self.lfplugin.config.pluginmanager.register(
                         LFPluginCollSkipfiles(self.lfplugin), "lfplugin-collskip"
                     )
@@ -254,22 +294,19 @@ def pytest_make_collect_report(self, collector: nodes.Collector):
                     # Keep all sub-collectors.
                     or isinstance(x, nodes.Collector)
                 ]
-                return
-        yield
+
+        return res
 
 
 class LFPluginCollSkipfiles:
-    def __init__(self, lfplugin: "LFPlugin") -> None:
+    def __init__(self, lfplugin: LFPlugin) -> None:
         self.lfplugin = lfplugin
 
     @hookimpl
     def pytest_make_collect_report(
         self, collector: nodes.Collector
-    ) -> Optional[CollectReport]:
-        # Packages are Modules, but _last_failed_paths only contains
-        # test-bearing paths and doesn't try to include the paths of their
-        # packages, so don't filter them.
-        if isinstance(collector, Module) and not isinstance(collector, Package):
+    ) -> CollectReport | None:
+        if isinstance(collector, File):
             if collector.path not in self.lfplugin._last_failed_paths:
                 self.lfplugin._skipped_files += 1
 
@@ -287,9 +324,9 @@ def __init__(self, config: Config) -> None:
         active_keys = "lf", "failedfirst"
         self.active = any(config.getoption(key) for key in active_keys)
         assert config.cache
-        self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {})
-        self._previously_failed_count: Optional[int] = None
-        self._report_status: Optional[str] = None
+        self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {})
+        self._previously_failed_count: int | None = None
+        self._report_status: str | None = None
         self._skipped_files = 0  # count skipped files during collection due to --lf
 
         if config.getoption("lf"):
@@ -298,15 +335,20 @@ def __init__(self, config: Config) -> None:
                 LFPluginCollWrapper(self), "lfplugin-collwrapper"
             )
 
-    def get_last_failed_paths(self) -> Set[Path]:
-        """Return a set with all Paths()s of the previously failed nodeids."""
+    def get_last_failed_paths(self) -> set[Path]:
+        """Return a set with all Paths of the previously failed nodeids and
+        their parents."""
         rootpath = self.config.rootpath
-        result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed}
+        result = set()
+        for nodeid in self.lastfailed:
+            path = rootpath / nodeid.split("::")[0]
+            result.add(path)
+            result.update(path.parents)
         return {x for x in result if x.exists()}
 
-    def pytest_report_collectionfinish(self) -> Optional[str]:
-        if self.active and self.config.getoption("verbose") >= 0:
-            return "run-last-failure: %s" % self._report_status
+    def pytest_report_collectionfinish(self) -> str | None:
+        if self.active and self.config.get_verbosity() >= 0:
+            return f"run-last-failure: {self._report_status}"
         return None
 
     def pytest_runtest_logreport(self, report: TestReport) -> None:
@@ -324,14 +366,14 @@ def pytest_collectreport(self, report: CollectReport) -> None:
         else:
             self.lastfailed[report.nodeid] = True
 
-    @hookimpl(hookwrapper=True, tryfirst=True)
+    @hookimpl(wrapper=True, tryfirst=True)
     def pytest_collection_modifyitems(
-        self, config: Config, items: List[nodes.Item]
-    ) -> Generator[None, None, None]:
-        yield
+        self, config: Config, items: list[nodes.Item]
+    ) -> Generator[None]:
+        res = yield
 
         if not self.active:
-            return
+            return res
 
         if self.lastfailed:
             previously_failed = []
@@ -346,8 +388,8 @@ def pytest_collection_modifyitems(
             if not previously_failed:
                 # Running a subset of all tests with recorded failures
                 # only outside of it.
-                self._report_status = "%d known failures not in selected tests" % (
-                    len(self.lastfailed),
+                self._report_status = (
+                    f"{len(self.lastfailed)} known failures not in selected tests"
                 )
             else:
                 if self.config.getoption("lf"):
@@ -358,15 +400,13 @@ def pytest_collection_modifyitems(
 
                 noun = "failure" if self._previously_failed_count == 1 else "failures"
                 suffix = " first" if self.config.getoption("failedfirst") else ""
-                self._report_status = "rerun previous {count} {noun}{suffix}".format(
-                    count=self._previously_failed_count, suffix=suffix, noun=noun
+                self._report_status = (
+                    f"rerun previous {self._previously_failed_count} {noun}{suffix}"
                 )
 
             if self._skipped_files > 0:
                 files_noun = "file" if self._skipped_files == 1 else "files"
-                self._report_status += " (skipped {files} {files_noun})".format(
-                    files=self._skipped_files, files_noun=files_noun
-                )
+                self._report_status += f" (skipped {self._skipped_files} {files_noun})"
         else:
             self._report_status = "no previously failed tests, "
             if self.config.getoption("last_failed_no_failures") == "none":
@@ -376,6 +416,8 @@ def pytest_collection_modifyitems(
             else:
                 self._report_status += "not deselecting items."
 
+        return res
+
     def pytest_sessionfinish(self, session: Session) -> None:
         config = self.config
         if config.getoption("cacheshow") or hasattr(config, "workerinput"):
@@ -396,15 +438,13 @@ def __init__(self, config: Config) -> None:
         assert config.cache is not None
         self.cached_nodeids = set(config.cache.get("cache/nodeids", []))
 
-    @hookimpl(hookwrapper=True, tryfirst=True)
-    def pytest_collection_modifyitems(
-        self, items: List[nodes.Item]
-    ) -> Generator[None, None, None]:
-        yield
+    @hookimpl(wrapper=True, tryfirst=True)
+    def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> Generator[None]:
+        res = yield
 
         if self.active:
-            new_items: Dict[str, nodes.Item] = {}
-            other_items: Dict[str, nodes.Item] = {}
+            new_items: dict[str, nodes.Item] = {}
+            other_items: dict[str, nodes.Item] = {}
             for item in items:
                 if item.nodeid not in self.cached_nodeids:
                     new_items[item.nodeid] = item
@@ -418,8 +458,10 @@ def pytest_collection_modifyitems(
         else:
             self.cached_nodeids.update(item.nodeid for item in items)
 
-    def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]:
-        return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True)  # type: ignore[no-any-return]
+        return res
+
+    def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]:
+        return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True)
 
     def pytest_sessionfinish(self) -> None:
         config = self.config
@@ -440,15 +482,14 @@ def pytest_addoption(parser: Parser) -> None:
         "--last-failed",
         action="store_true",
         dest="lf",
-        help="rerun only the tests that failed "
-        "at the last run (or all if none failed)",
+        help="Rerun only the tests that failed at the last run (or all if none failed)",
     )
     group.addoption(
         "--ff",
         "--failed-first",
         action="store_true",
         dest="failedfirst",
-        help="run all tests, but run the last failures first.\n"
+        help="Run all tests, but run the last failures first. "
         "This may re-order tests and thus lead to "
         "repeated fixture setup/teardown.",
     )
@@ -457,7 +498,7 @@ def pytest_addoption(parser: Parser) -> None:
         "--new-first",
         action="store_true",
         dest="newfirst",
-        help="run tests from new files first, then the rest of the tests "
+        help="Run tests from new files first, then the rest of the tests "
         "sorted by file mtime",
     )
     group.addoption(
@@ -466,7 +507,7 @@ def pytest_addoption(parser: Parser) -> None:
         nargs="?",
         dest="cacheshow",
         help=(
-            "show cache contents, don't perform collection or tests. "
+            "Show cache contents, don't perform collection or tests. "
             "Optional argument: glob (default: '*')."
         ),
     )
@@ -474,12 +515,12 @@ def pytest_addoption(parser: Parser) -> None:
         "--cache-clear",
         action="store_true",
         dest="cacheclear",
-        help="remove all cache contents at start of test run.",
+        help="Remove all cache contents at start of test run",
     )
     cache_dir_default = ".pytest_cache"
     if "TOX_ENV_DIR" in os.environ:
         cache_dir_default = os.path.join(os.environ["TOX_ENV_DIR"], cache_dir_default)
-    parser.addini("cache_dir", default=cache_dir_default, help="cache directory path.")
+    parser.addini("cache_dir", default=cache_dir_default, help="Cache directory path")
     group.addoption(
         "--lfnf",
         "--last-failed-no-failures",
@@ -487,12 +528,16 @@ def pytest_addoption(parser: Parser) -> None:
         dest="last_failed_no_failures",
         choices=("all", "none"),
         default="all",
-        help="which tests to run with no previously (known) failures.",
+        help="With ``--lf``, determines whether to execute tests when there "
+        "are no previously (known) failures or when no "
+        "cached ``lastfailed`` data was found. "
+        "``all`` (the default) runs the full test suite again. "
+        "``none`` just emits a message about no known failures and exits successfully.",
     )
 
 
-def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
-    if config.option.cacheshow:
+def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
+    if config.option.cacheshow and not config.option.help:
         from _pytest.main import wrap_session
 
         return wrap_session(config, cacheshow)
@@ -522,7 +567,7 @@ def cache(request: FixtureRequest) -> Cache:
     return request.config.cache
 
 
-def pytest_report_header(config: Config) -> Optional[str]:
+def pytest_report_header(config: Config) -> str | None:
     """Display cachedir with --cache-show and if non-default."""
     if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache":
         assert config.cache is not None
@@ -556,25 +601,25 @@ def cacheshow(config: Config, session: Session) -> int:
     dummy = object()
     basedir = config.cache._cachedir
     vdir = basedir / Cache._CACHE_PREFIX_VALUES
-    tw.sep("-", "cache values for %r" % glob)
+    tw.sep("-", f"cache values for {glob!r}")
     for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()):
         key = str(valpath.relative_to(vdir))
         val = config.cache.get(key, dummy)
         if val is dummy:
-            tw.line("%s contains unreadable content, will be ignored" % key)
+            tw.line(f"{key} contains unreadable content, will be ignored")
         else:
-            tw.line("%s contains:" % key)
+            tw.line(f"{key} contains:")
             for line in pformat(val).splitlines():
                 tw.line("  " + line)
 
     ddir = basedir / Cache._CACHE_PREFIX_DIRS
     if ddir.is_dir():
         contents = sorted(ddir.rglob(glob))
-        tw.sep("-", "cache directories for %r" % glob)
+        tw.sep("-", f"cache directories for {glob!r}")
         for p in contents:
             # if p.is_dir():
             #    print("%s/" % p.relative_to(basedir))
             if p.is_file():
                 key = str(p.relative_to(basedir))
-                tw.line(f"{key} is a file of length {p.stat().st_size:d}")
+                tw.line(f"{key} is a file of length {p.stat().st_size}")
     return 0
diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py
index 884f035e299..3812d88176a 100644
--- a/src/_pytest/capture.py
+++ b/src/_pytest/capture.py
@@ -1,23 +1,36 @@
+# mypy: allow-untyped-defs
 """Per-test stdout/stderr capturing mechanism."""
+
+from __future__ import annotations
+
+import abc
+import collections
+from collections.abc import Generator
+from collections.abc import Iterable
+from collections.abc import Iterator
 import contextlib
-import functools
 import io
+from io import UnsupportedOperation
 import os
 import sys
-from io import UnsupportedOperation
 from tempfile import TemporaryFile
+from types import TracebackType
 from typing import Any
 from typing import AnyStr
-from typing import Generator
+from typing import BinaryIO
+from typing import cast
+from typing import Final
+from typing import final
 from typing import Generic
-from typing import Iterator
-from typing import Optional
+from typing import Literal
+from typing import NamedTuple
 from typing import TextIO
-from typing import Tuple
 from typing import TYPE_CHECKING
-from typing import Union
 
-from _pytest.compat import final
+
+if TYPE_CHECKING:
+    from typing_extensions import Self
+
 from _pytest.config import Config
 from _pytest.config import hookimpl
 from _pytest.config.argparsing import Parser
@@ -27,29 +40,28 @@
 from _pytest.nodes import Collector
 from _pytest.nodes import File
 from _pytest.nodes import Item
+from _pytest.reports import CollectReport
 
-if TYPE_CHECKING:
-    from typing_extensions import Literal
 
-    _CaptureMethod = Literal["fd", "sys", "no", "tee-sys"]
+_CaptureMethod = Literal["fd", "sys", "no", "tee-sys"]
 
 
 def pytest_addoption(parser: Parser) -> None:
     group = parser.getgroup("general")
-    group._addoption(
+    group.addoption(
         "--capture",
         action="store",
         default="fd",
         metavar="method",
         choices=["fd", "sys", "no", "tee-sys"],
-        help="per-test capturing method: one of fd|sys|no|tee-sys.",
+        help="Per-test capturing method: one of fd|sys|no|tee-sys",
     )
-    group._addoption(
+    group._addoption(  # private to use reserved lower-case short option
         "-s",
         action="store_const",
         const="no",
         dest="capture",
-        help="shortcut for --capture=no.",
+        help="Shortcut for --capture=no",
     )
 
 
@@ -68,8 +80,25 @@ def _colorama_workaround() -> None:
             pass
 
 
-def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
-    """Workaround for Windows Unicode console handling on Python>=3.6.
+def _readline_workaround() -> None:
+    """Ensure readline is imported early so it attaches to the correct stdio handles.
+
+    This isn't a problem with the default GNU readline implementation, but in
+    some configurations, Python uses libedit instead (on macOS, and for prebuilt
+    binaries such as used by uv).
+
+    In theory this is only needed if readline.backend == "libedit", but the
+    workaround consists of importing readline here, so we already worked around
+    the issue by the time we could check if we need to.
+    """
+    try:
+        import readline  # noqa: F401
+    except ImportError:
+        pass
+
+
+def _windowsconsoleio_workaround(stream: TextIO) -> None:
+    """Workaround for Windows Unicode console handling.
 
     Python 3.6 implemented Unicode console handling for Windows. This works
     by reading/writing to the raw console handle using
@@ -96,23 +125,22 @@ def _py36_windowsconsoleio_workaround(stream: TextIO) -> None:
         return
 
     # Bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666).
-    if not hasattr(stream, "buffer"):  # type: ignore[unreachable]
+    if not hasattr(stream, "buffer"):  # type: ignore[unreachable,unused-ignore]
         return
 
-    buffered = hasattr(stream.buffer, "raw")
-    raw_stdout = stream.buffer.raw if buffered else stream.buffer  # type: ignore[attr-defined]
+    raw_stdout = stream.buffer.raw if hasattr(stream.buffer, "raw") else stream.buffer
 
-    if not isinstance(raw_stdout, io._WindowsConsoleIO):  # type: ignore[attr-defined]
+    if not isinstance(raw_stdout, io._WindowsConsoleIO):  # type: ignore[attr-defined,unused-ignore]
         return
 
     def _reopen_stdio(f, mode):
-        if not buffered and mode[0] == "w":
+        if not hasattr(stream.buffer, "raw") and mode[0] == "w":
             buffering = 0
         else:
             buffering = -1
 
         return io.TextIOWrapper(
-            open(os.dup(f.fileno()), mode, buffering),  # type: ignore[arg-type]
+            open(os.dup(f.fileno()), mode, buffering),
             f.encoding,
             f.errors,
             f.newlines,
@@ -124,12 +152,13 @@ def _reopen_stdio(f, mode):
     sys.stderr = _reopen_stdio(sys.stderr, "wb")
 
 
-@hookimpl(hookwrapper=True)
-def pytest_load_initial_conftests(early_config: Config):
+@hookimpl(wrapper=True)
+def pytest_load_initial_conftests(early_config: Config) -> Generator[None]:
     ns = early_config.known_args_namespace
     if ns.capture == "fd":
-        _py36_windowsconsoleio_workaround(sys.stdout)
+        _windowsconsoleio_workaround(sys.stdout)
     _colorama_workaround()
+    _readline_workaround()
     pluginmanager = early_config.pluginmanager
     capman = CaptureManager(ns.capture)
     pluginmanager.register(capman, "capturemanager")
@@ -139,12 +168,16 @@ def pytest_load_initial_conftests(early_config: Config):
 
     # Finally trigger conftest loading but while capturing (issue #93).
     capman.start_global_capturing()
-    outcome = yield
-    capman.suspend_global_capture()
-    if outcome.excinfo is not None:
+    try:
+        try:
+            yield
+        finally:
+            capman.suspend_global_capture()
+    except BaseException:
         out, err = capman.read_global_capture()
         sys.stdout.write(out)
         sys.stderr.write(err)
+        raise
 
 
 # IO Helpers.
@@ -163,7 +196,8 @@ def name(self) -> str:
     def mode(self) -> str:
         # TextIOWrapper doesn't expose a mode, but at least some of our
         # tests check it.
-        return self.buffer.mode.replace("b", "")
+        assert hasattr(self.buffer, "mode")
+        return cast(str, self.buffer.mode.replace("b", ""))
 
 
 class CaptureIO(io.TextIOWrapper):
@@ -185,53 +219,152 @@ def write(self, s: str) -> int:
         return self._other.write(s)
 
 
-class DontReadFromInput:
-    encoding = None
+class DontReadFromInput(TextIO):
+    @property
+    def encoding(self) -> str:
+        assert sys.__stdin__ is not None
+        return sys.__stdin__.encoding
 
-    def read(self, *args):
+    def read(self, size: int = -1) -> str:
         raise OSError(
             "pytest: reading from stdin while output is captured!  Consider using `-s`."
         )
 
     readline = read
-    readlines = read
-    __next__ = read
 
-    def __iter__(self):
+    def __next__(self) -> str:
+        return self.readline()
+
+    def readlines(self, hint: int | None = -1) -> list[str]:
+        raise OSError(
+            "pytest: reading from stdin while output is captured!  Consider using `-s`."
+        )
+
+    def __iter__(self) -> Iterator[str]:
         return self
 
     def fileno(self) -> int:
         raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()")
 
+    def flush(self) -> None:
+        raise UnsupportedOperation("redirected stdin is pseudofile, has no flush()")
+
     def isatty(self) -> bool:
         return False
 
     def close(self) -> None:
         pass
 
-    @property
-    def buffer(self):
+    def readable(self) -> bool:
+        return False
+
+    def seek(self, offset: int, whence: int = 0) -> int:
+        raise UnsupportedOperation("redirected stdin is pseudofile, has no seek(int)")
+
+    def seekable(self) -> bool:
+        return False
+
+    def tell(self) -> int:
+        raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()")
+
+    def truncate(self, size: int | None = None) -> int:
+        raise UnsupportedOperation("cannot truncate stdin")
+
+    def write(self, data: str) -> int:
+        raise UnsupportedOperation("cannot write to stdin")
+
+    def writelines(self, lines: Iterable[str]) -> None:
+        raise UnsupportedOperation("Cannot write to stdin")
+
+    def writable(self) -> bool:
+        return False
+
+    def __enter__(self) -> Self:
         return self
 
+    def __exit__(
+        self,
+        type: type[BaseException] | None,
+        value: BaseException | None,
+        traceback: TracebackType | None,
+    ) -> None:
+        pass
+
+    @property
+    def buffer(self) -> BinaryIO:
+        # The str/bytes doesn't actually matter in this type, so OK to fake.
+        return self  # type: ignore[return-value]
+
 
 # Capture classes.
 
 
+class CaptureBase(abc.ABC, Generic[AnyStr]):
+    EMPTY_BUFFER: AnyStr
+
+    @abc.abstractmethod
+    def __init__(self, fd: int) -> None:
+        raise NotImplementedError()
+
+    @abc.abstractmethod
+    def start(self) -> None:
+        raise NotImplementedError()
+
+    @abc.abstractmethod
+    def done(self) -> None:
+        raise NotImplementedError()
+
+    @abc.abstractmethod
+    def suspend(self) -> None:
+        raise NotImplementedError()
+
+    @abc.abstractmethod
+    def resume(self) -> None:
+        raise NotImplementedError()
+
+    @abc.abstractmethod
+    def writeorg(self, data: AnyStr) -> None:
+        raise NotImplementedError()
+
+    @abc.abstractmethod
+    def snap(self) -> AnyStr:
+        raise NotImplementedError()
+
+
 patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
 
 
-class NoCapture:
-    EMPTY_BUFFER = None
-    __init__ = start = done = suspend = resume = lambda *args: None
+class NoCapture(CaptureBase[str]):
+    EMPTY_BUFFER = ""
 
+    def __init__(self, fd: int) -> None:
+        pass
 
-class SysCaptureBinary:
+    def start(self) -> None:
+        pass
 
-    EMPTY_BUFFER = b""
+    def done(self) -> None:
+        pass
+
+    def suspend(self) -> None:
+        pass
+
+    def resume(self) -> None:
+        pass
+
+    def snap(self) -> str:
+        return ""
+
+    def writeorg(self, data: str) -> None:
+        pass
 
-    def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None:
+
+class SysCaptureBase(CaptureBase[AnyStr]):
+    def __init__(
+        self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False
+    ) -> None:
         name = patchsysdict[fd]
-        self._old = getattr(sys, name)
+        self._old: TextIO = getattr(sys, name)
         self.name = name
         if tmpfile is None:
             if name == "stdin":
@@ -245,7 +378,7 @@ def repr(self, class_name: str) -> str:
         return "<{} {} _old={} _state={!r} tmpfile={!r}>".format(
             class_name,
             self.name,
-            hasattr(self, "_old") and repr(self._old) or "<UNSET>",
+            (hasattr(self, "_old") and repr(self._old)) or "<UNSET>",
             self._state,
             self.tmpfile,
         )
@@ -254,16 +387,16 @@ def __repr__(self) -> str:
         return "<{} {} _old={} _state={!r} tmpfile={!r}>".format(
             self.__class__.__name__,
             self.name,
-            hasattr(self, "_old") and repr(self._old) or "<UNSET>",
+            (hasattr(self, "_old") and repr(self._old)) or "<UNSET>",
             self._state,
             self.tmpfile,
         )
 
-    def _assert_state(self, op: str, states: Tuple[str, ...]) -> None:
-        assert (
-            self._state in states
-        ), "cannot {} in state {!r}: expected one of {}".format(
-            op, self._state, ", ".join(states)
+    def _assert_state(self, op: str, states: tuple[str, ...]) -> None:
+        assert self._state in states, (
+            "cannot {} in state {!r}: expected one of {}".format(
+                op, self._state, ", ".join(states)
+            )
         )
 
     def start(self) -> None:
@@ -271,14 +404,6 @@ def start(self) -> None:
         setattr(sys, self.name, self.tmpfile)
         self._state = "started"
 
-    def snap(self):
-        self._assert_state("snap", ("started", "suspended"))
-        self.tmpfile.seek(0)
-        res = self.tmpfile.buffer.read()
-        self.tmpfile.seek(0)
-        self.tmpfile.truncate()
-        return res
-
     def done(self) -> None:
         self._assert_state("done", ("initialized", "started", "suspended", "done"))
         if self._state == "done":
@@ -300,36 +425,43 @@ def resume(self) -> None:
         setattr(sys, self.name, self.tmpfile)
         self._state = "started"
 
-    def writeorg(self, data) -> None:
+
+class SysCaptureBinary(SysCaptureBase[bytes]):
+    EMPTY_BUFFER = b""
+
+    def snap(self) -> bytes:
+        self._assert_state("snap", ("started", "suspended"))
+        self.tmpfile.seek(0)
+        res = self.tmpfile.buffer.read()
+        self.tmpfile.seek(0)
+        self.tmpfile.truncate()
+        return res
+
+    def writeorg(self, data: bytes) -> None:
         self._assert_state("writeorg", ("started", "suspended"))
         self._old.flush()
         self._old.buffer.write(data)
         self._old.buffer.flush()
 
 
-class SysCapture(SysCaptureBinary):
-    EMPTY_BUFFER = ""  # type: ignore[assignment]
+class SysCapture(SysCaptureBase[str]):
+    EMPTY_BUFFER = ""
 
-    def snap(self):
+    def snap(self) -> str:
+        self._assert_state("snap", ("started", "suspended"))
+        assert isinstance(self.tmpfile, CaptureIO)
         res = self.tmpfile.getvalue()
         self.tmpfile.seek(0)
         self.tmpfile.truncate()
         return res
 
-    def writeorg(self, data):
+    def writeorg(self, data: str) -> None:
         self._assert_state("writeorg", ("started", "suspended"))
         self._old.write(data)
         self._old.flush()
 
 
-class FDCaptureBinary:
-    """Capture IO to/from a given OS-level file descriptor.
-
-    snap() produces `bytes`.
-    """
-
-    EMPTY_BUFFER = b""
-
+class FDCaptureBase(CaptureBase[AnyStr]):
     def __init__(self, targetfd: int) -> None:
         self.targetfd = targetfd
 
@@ -347,15 +479,15 @@ def __init__(self, targetfd: int) -> None:
             # Further complications are the need to support suspend() and the
             # possibility of FD reuse (e.g. the tmpfile getting the very same
             # target FD). The following approach is robust, I believe.
-            self.targetfd_invalid: Optional[int] = os.open(os.devnull, os.O_RDWR)
+            self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR)
             os.dup2(self.targetfd_invalid, targetfd)
         else:
             self.targetfd_invalid = None
         self.targetfd_save = os.dup(targetfd)
 
         if targetfd == 0:
-            self.tmpfile = open(os.devnull)
-            self.syscapture = SysCapture(targetfd)
+            self.tmpfile = open(os.devnull, encoding="utf-8")
+            self.syscapture: CaptureBase[str] = SysCapture(targetfd)
         else:
             self.tmpfile = EncodedFile(
                 TemporaryFile(buffering=0),
@@ -367,24 +499,21 @@ def __init__(self, targetfd: int) -> None:
             if targetfd in patchsysdict:
                 self.syscapture = SysCapture(targetfd, self.tmpfile)
             else:
-                self.syscapture = NoCapture()
+                self.syscapture = NoCapture(targetfd)
 
         self._state = "initialized"
 
     def __repr__(self) -> str:
-        return "<{} {} oldfd={} _state={!r} tmpfile={!r}>".format(
-            self.__class__.__name__,
-            self.targetfd,
-            self.targetfd_save,
-            self._state,
-            self.tmpfile,
+        return (
+            f"<{self.__class__.__name__} {self.targetfd} oldfd={self.targetfd_save} "
+            f"_state={self._state!r} tmpfile={self.tmpfile!r}>"
         )
 
-    def _assert_state(self, op: str, states: Tuple[str, ...]) -> None:
-        assert (
-            self._state in states
-        ), "cannot {} in state {!r}: expected one of {}".format(
-            op, self._state, ", ".join(states)
+    def _assert_state(self, op: str, states: tuple[str, ...]) -> None:
+        assert self._state in states, (
+            "cannot {} in state {!r}: expected one of {}".format(
+                op, self._state, ", ".join(states)
+            )
         )
 
     def start(self) -> None:
@@ -394,14 +523,6 @@ def start(self) -> None:
         self.syscapture.start()
         self._state = "started"
 
-    def snap(self):
-        self._assert_state("snap", ("started", "suspended"))
-        self.tmpfile.seek(0)
-        res = self.tmpfile.buffer.read()
-        self.tmpfile.seek(0)
-        self.tmpfile.truncate()
-        return res
-
     def done(self) -> None:
         """Stop capturing, restore streams, return original capture file,
         seeked to position zero."""
@@ -434,22 +555,38 @@ def resume(self) -> None:
         os.dup2(self.tmpfile.fileno(), self.targetfd)
         self._state = "started"
 
-    def writeorg(self, data):
+
+class FDCaptureBinary(FDCaptureBase[bytes]):
+    """Capture IO to/from a given OS-level file descriptor.
+
+    snap() produces `bytes`.
+    """
+
+    EMPTY_BUFFER = b""
+
+    def snap(self) -> bytes:
+        self._assert_state("snap", ("started", "suspended"))
+        self.tmpfile.seek(0)
+        res = self.tmpfile.buffer.read()
+        self.tmpfile.seek(0)
+        self.tmpfile.truncate()
+        return res  # type: ignore[return-value]
+
+    def writeorg(self, data: bytes) -> None:
         """Write to original file descriptor."""
         self._assert_state("writeorg", ("started", "suspended"))
         os.write(self.targetfd_save, data)
 
 
-class FDCapture(FDCaptureBinary):
+class FDCapture(FDCaptureBase[str]):
     """Capture IO to/from a given OS-level file descriptor.
 
     snap() produces text.
     """
 
-    # Ignore type because it doesn't match the type in the superclass (bytes).
-    EMPTY_BUFFER = ""  # type: ignore
+    EMPTY_BUFFER = ""
 
-    def snap(self):
+    def snap(self) -> str:
         self._assert_state("snap", ("started", "suspended"))
         self.tmpfile.seek(0)
         res = self.tmpfile.read()
@@ -457,85 +594,55 @@ def snap(self):
         self.tmpfile.truncate()
         return res
 
-    def writeorg(self, data):
+    def writeorg(self, data: str) -> None:
         """Write to original file descriptor."""
-        super().writeorg(data.encode("utf-8"))  # XXX use encoding of original stream
+        self._assert_state("writeorg", ("started", "suspended"))
+        # XXX use encoding of original stream
+        os.write(self.targetfd_save, data.encode("utf-8"))
 
 
 # MultiCapture
 
 
-# This class was a namedtuple, but due to mypy limitation[0] it could not be
-# made generic, so was replaced by a regular class which tries to emulate the
-# pertinent parts of a namedtuple. If the mypy limitation is ever lifted, can
-# make it a namedtuple again.
-# [0]: https://github.com/python/mypy/issues/685
-@final
-@functools.total_ordering
-class CaptureResult(Generic[AnyStr]):
-    """The result of :method:`CaptureFixture.readouterr`."""
-
-    __slots__ = ("out", "err")
+# Generic NamedTuple only supported since Python 3.11.
+if sys.version_info >= (3, 11) or TYPE_CHECKING:
 
-    def __init__(self, out: AnyStr, err: AnyStr) -> None:
-        self.out: AnyStr = out
-        self.err: AnyStr = err
+    @final
+    class CaptureResult(NamedTuple, Generic[AnyStr]):
+        """The result of :method:`caplog.readouterr() <pytest.CaptureFixture.readouterr>`."""
 
-    def __len__(self) -> int:
-        return 2
+        out: AnyStr
+        err: AnyStr
 
-    def __iter__(self) -> Iterator[AnyStr]:
-        return iter((self.out, self.err))
+else:
 
-    def __getitem__(self, item: int) -> AnyStr:
-        return tuple(self)[item]
+    class CaptureResult(
+        collections.namedtuple("CaptureResult", ["out", "err"]),  # noqa: PYI024
+        Generic[AnyStr],
+    ):
+        """The result of :method:`caplog.readouterr() <pytest.CaptureFixture.readouterr>`."""
 
-    def _replace(
-        self, *, out: Optional[AnyStr] = None, err: Optional[AnyStr] = None
-    ) -> "CaptureResult[AnyStr]":
-        return CaptureResult(
-            out=self.out if out is None else out, err=self.err if err is None else err
-        )
-
-    def count(self, value: AnyStr) -> int:
-        return tuple(self).count(value)
-
-    def index(self, value) -> int:
-        return tuple(self).index(value)
-
-    def __eq__(self, other: object) -> bool:
-        if not isinstance(other, (CaptureResult, tuple)):
-            return NotImplemented
-        return tuple(self) == tuple(other)
-
-    def __hash__(self) -> int:
-        return hash(tuple(self))
-
-    def __lt__(self, other: object) -> bool:
-        if not isinstance(other, (CaptureResult, tuple)):
-            return NotImplemented
-        return tuple(self) < tuple(other)
-
-    def __repr__(self) -> str:
-        return f"CaptureResult(out={self.out!r}, err={self.err!r})"
+        __slots__ = ()
 
 
 class MultiCapture(Generic[AnyStr]):
     _state = None
     _in_suspended = False
 
-    def __init__(self, in_, out, err) -> None:
-        self.in_ = in_
-        self.out = out
-        self.err = err
+    def __init__(
+        self,
+        in_: CaptureBase[AnyStr] | None,
+        out: CaptureBase[AnyStr] | None,
+        err: CaptureBase[AnyStr] | None,
+    ) -> None:
+        self.in_: CaptureBase[AnyStr] | None = in_
+        self.out: CaptureBase[AnyStr] | None = out
+        self.err: CaptureBase[AnyStr] | None = err
 
     def __repr__(self) -> str:
-        return "<MultiCapture out={!r} err={!r} in_={!r} _state={!r} _in_suspended={!r}>".format(
-            self.out,
-            self.err,
-            self.in_,
-            self._state,
-            self._in_suspended,
+        return (
+            f"<MultiCapture out={self.out!r} err={self.err!r} in_={self.in_!r} "
+            f"_state={self._state!r} _in_suspended={self._in_suspended!r}>"
         )
 
     def start_capturing(self) -> None:
@@ -547,12 +654,14 @@ def start_capturing(self) -> None:
         if self.err:
             self.err.start()
 
-    def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]:
+    def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]:
         """Pop current snapshot out/err capture and flush to orig streams."""
         out, err = self.readouterr()
         if out:
+            assert self.out is not None
             self.out.writeorg(out)
         if err:
+            assert self.err is not None
             self.err.writeorg(err)
         return out, err
 
@@ -573,6 +682,7 @@ def resume_capturing(self) -> None:
         if self.err:
             self.err.resume()
         if self._in_suspended:
+            assert self.in_ is not None
             self.in_.resume()
             self._in_suspended = False
 
@@ -595,10 +705,11 @@ def is_started(self) -> bool:
     def readouterr(self) -> CaptureResult[AnyStr]:
         out = self.out.snap() if self.out else ""
         err = self.err.snap() if self.err else ""
-        return CaptureResult(out, err)
+        # TODO: This type error is real, need to fix.
+        return CaptureResult(out, err)  # type: ignore[arg-type]
 
 
-def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]:
+def _get_multicapture(method: _CaptureMethod) -> MultiCapture[str]:
     if method == "fd":
         return MultiCapture(in_=FDCapture(0), out=FDCapture(1), err=FDCapture(2))
     elif method == "sys":
@@ -634,21 +745,22 @@ class CaptureManager:
       needed to ensure the fixtures take precedence over the global capture.
     """
 
-    def __init__(self, method: "_CaptureMethod") -> None:
-        self._method = method
-        self._global_capturing: Optional[MultiCapture[str]] = None
-        self._capture_fixture: Optional[CaptureFixture[Any]] = None
+    def __init__(self, method: _CaptureMethod) -> None:
+        self._method: Final = method
+        self._global_capturing: MultiCapture[str] | None = None
+        self._capture_fixture: CaptureFixture[Any] | None = None
 
     def __repr__(self) -> str:
-        return "<CaptureManager _method={!r} _global_capturing={!r} _capture_fixture={!r}>".format(
-            self._method, self._global_capturing, self._capture_fixture
+        return (
+            f"<CaptureManager _method={self._method!r} _global_capturing={self._global_capturing!r} "
+            f"_capture_fixture={self._capture_fixture!r}>"
         )
 
-    def is_capturing(self) -> Union[str, bool]:
+    def is_capturing(self) -> str | bool:
         if self.is_globally_capturing():
             return "global"
         if self._capture_fixture:
-            return "fixture %s" % self._capture_fixture.request.fixturename
+            return f"fixture {self._capture_fixture.request.fixturename}"
         return False
 
     # Global capturing control
@@ -692,14 +804,12 @@ def read_global_capture(self) -> CaptureResult[str]:
 
     # Fixture Control
 
-    def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None:
+    def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None:
         if self._capture_fixture:
             current_fixture = self._capture_fixture.request.fixturename
             requested_fixture = capture_fixture.request.fixturename
             capture_fixture.request.raiseerror(
-                "cannot use {} and {} at the same time".format(
-                    requested_fixture, current_fixture
-                )
+                f"cannot use {requested_fixture} and {current_fixture} at the same time"
             )
         self._capture_fixture = capture_fixture
 
@@ -728,7 +838,7 @@ def resume_fixture(self) -> None:
     # Helper context managers
 
     @contextlib.contextmanager
-    def global_and_fixture_disabled(self) -> Generator[None, None, None]:
+    def global_and_fixture_disabled(self) -> Generator[None]:
         """Context manager to temporarily disable global and current fixture capturing."""
         do_fixture = self._capture_fixture and self._capture_fixture._is_started()
         if do_fixture:
@@ -745,7 +855,7 @@ def global_and_fixture_disabled(self) -> Generator[None, None, None]:
                 self.resume_fixture()
 
     @contextlib.contextmanager
-    def item_capture(self, when: str, item: Item) -> Generator[None, None, None]:
+    def item_capture(self, when: str, item: Item) -> Generator[None]:
         self.resume_global_capture()
         self.activate_fixture()
         try:
@@ -754,41 +864,45 @@ def item_capture(self, when: str, item: Item) -> Generator[None, None, None]:
             self.deactivate_fixture()
             self.suspend_global_capture(in_=False)
 
-        out, err = self.read_global_capture()
-        item.add_report_section(when, "stdout", out)
-        item.add_report_section(when, "stderr", err)
+            out, err = self.read_global_capture()
+            item.add_report_section(when, "stdout", out)
+            item.add_report_section(when, "stderr", err)
 
     # Hooks
 
-    @hookimpl(hookwrapper=True)
-    def pytest_make_collect_report(self, collector: Collector):
+    @hookimpl(wrapper=True)
+    def pytest_make_collect_report(
+        self, collector: Collector
+    ) -> Generator[None, CollectReport, CollectReport]:
         if isinstance(collector, File):
             self.resume_global_capture()
-            outcome = yield
-            self.suspend_global_capture()
+            try:
+                rep = yield
+            finally:
+                self.suspend_global_capture()
             out, err = self.read_global_capture()
-            rep = outcome.get_result()
             if out:
                 rep.sections.append(("Captured stdout", out))
             if err:
                 rep.sections.append(("Captured stderr", err))
         else:
-            yield
+            rep = yield
+        return rep
 
-    @hookimpl(hookwrapper=True)
-    def pytest_runtest_setup(self, item: Item) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_runtest_setup(self, item: Item) -> Generator[None]:
         with self.item_capture("setup", item):
-            yield
+            return (yield)
 
-    @hookimpl(hookwrapper=True)
-    def pytest_runtest_call(self, item: Item) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_runtest_call(self, item: Item) -> Generator[None]:
         with self.item_capture("call", item):
-            yield
+            return (yield)
 
-    @hookimpl(hookwrapper=True)
-    def pytest_runtest_teardown(self, item: Item) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_runtest_teardown(self, item: Item) -> Generator[None]:
         with self.item_capture("teardown", item):
-            yield
+            return (yield)
 
     @hookimpl(tryfirst=True)
     def pytest_keyboard_interrupt(self) -> None:
@@ -804,21 +918,27 @@ class CaptureFixture(Generic[AnyStr]):
     :fixture:`capfd` and :fixture:`capfdbinary` fixtures."""
 
     def __init__(
-        self, captureclass, request: SubRequest, *, _ispytest: bool = False
+        self,
+        captureclass: type[CaptureBase[AnyStr]],
+        request: SubRequest,
+        *,
+        config: dict[str, Any] | None = None,
+        _ispytest: bool = False,
     ) -> None:
         check_ispytest(_ispytest)
-        self.captureclass = captureclass
+        self.captureclass: type[CaptureBase[AnyStr]] = captureclass
         self.request = request
-        self._capture: Optional[MultiCapture[AnyStr]] = None
-        self._captured_out = self.captureclass.EMPTY_BUFFER
-        self._captured_err = self.captureclass.EMPTY_BUFFER
+        self._config = config if config else {}
+        self._capture: MultiCapture[AnyStr] | None = None
+        self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER
+        self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER
 
     def _start(self) -> None:
         if self._capture is None:
             self._capture = MultiCapture(
                 in_=None,
-                out=self.captureclass(1),
-                err=self.captureclass(2),
+                out=self.captureclass(1, **self._config),
+                err=self.captureclass(2, **self._config),
             )
             self._capture.start_capturing()
 
@@ -864,9 +984,11 @@ def _is_started(self) -> bool:
         return False
 
     @contextlib.contextmanager
-    def disabled(self) -> Generator[None, None, None]:
+    def disabled(self) -> Generator[None]:
         """Temporarily disable capturing while inside the ``with`` block."""
-        capmanager = self.request.config.pluginmanager.getplugin("capturemanager")
+        capmanager: CaptureManager = self.request.config.pluginmanager.getplugin(
+            "capturemanager"
+        )
         with capmanager.global_and_fixture_disabled():
             yield
 
@@ -875,15 +997,26 @@ def disabled(self) -> Generator[None, None, None]:
 
 
 @fixture
-def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
-    """Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
+def capsys(request: SubRequest) -> Generator[CaptureFixture[str]]:
+    r"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
 
     The captured output is made available via ``capsys.readouterr()`` method
     calls, which return a ``(out, err)`` namedtuple.
     ``out`` and ``err`` will be ``text`` objects.
+
+    Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
+
+    Example:
+
+    .. code-block:: python
+
+        def test_output(capsys):
+            print("hello")
+            captured = capsys.readouterr()
+            assert captured.out == "hello\n"
     """
-    capman = request.config.pluginmanager.getplugin("capturemanager")
-    capture_fixture = CaptureFixture[str](SysCapture, request, _ispytest=True)
+    capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager")
+    capture_fixture = CaptureFixture(SysCapture, request, _ispytest=True)
     capman.set_fixture(capture_fixture)
     capture_fixture._start()
     yield capture_fixture
@@ -892,15 +1025,61 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
 
 
 @fixture
-def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
-    """Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
+def capteesys(request: SubRequest) -> Generator[CaptureFixture[str]]:
+    r"""Enable simultaneous text capturing and pass-through of writes
+    to ``sys.stdout`` and ``sys.stderr`` as defined by ``--capture=``.
+
+
+    The captured output is made available via ``capteesys.readouterr()`` method
+    calls, which return a ``(out, err)`` namedtuple.
+    ``out`` and ``err`` will be ``text`` objects.
+
+    The output is also passed-through, allowing it to be "live-printed",
+    reported, or both as defined by ``--capture=``.
+
+    Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
+
+    Example:
+
+    .. code-block:: python
+
+        def test_output(capsys):
+            print("hello")
+            captured = capteesys.readouterr()
+            assert captured.out == "hello\n"
+    """
+    capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager")
+    capture_fixture = CaptureFixture(
+        SysCapture, request, config=dict(tee=True), _ispytest=True
+    )
+    capman.set_fixture(capture_fixture)
+    capture_fixture._start()
+    yield capture_fixture
+    capture_fixture.close()
+    capman.unset_fixture()
+
+
+@fixture
+def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes]]:
+    r"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
 
     The captured output is made available via ``capsysbinary.readouterr()``
     method calls, which return a ``(out, err)`` namedtuple.
     ``out`` and ``err`` will be ``bytes`` objects.
+
+    Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
+
+    Example:
+
+    .. code-block:: python
+
+        def test_output(capsysbinary):
+            print("hello")
+            captured = capsysbinary.readouterr()
+            assert captured.out == b"hello\n"
     """
-    capman = request.config.pluginmanager.getplugin("capturemanager")
-    capture_fixture = CaptureFixture[bytes](SysCaptureBinary, request, _ispytest=True)
+    capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager")
+    capture_fixture = CaptureFixture(SysCaptureBinary, request, _ispytest=True)
     capman.set_fixture(capture_fixture)
     capture_fixture._start()
     yield capture_fixture
@@ -909,15 +1088,26 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None,
 
 
 @fixture
-def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
-    """Enable text capturing of writes to file descriptors ``1`` and ``2``.
+def capfd(request: SubRequest) -> Generator[CaptureFixture[str]]:
+    r"""Enable text capturing of writes to file descriptors ``1`` and ``2``.
 
     The captured output is made available via ``capfd.readouterr()`` method
     calls, which return a ``(out, err)`` namedtuple.
     ``out`` and ``err`` will be ``text`` objects.
+
+    Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
+
+    Example:
+
+    .. code-block:: python
+
+        def test_system_echo(capfd):
+            os.system('echo "hello"')
+            captured = capfd.readouterr()
+            assert captured.out == "hello\n"
     """
-    capman = request.config.pluginmanager.getplugin("capturemanager")
-    capture_fixture = CaptureFixture[str](FDCapture, request, _ispytest=True)
+    capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager")
+    capture_fixture = CaptureFixture(FDCapture, request, _ispytest=True)
     capman.set_fixture(capture_fixture)
     capture_fixture._start()
     yield capture_fixture
@@ -926,15 +1116,27 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
 
 
 @fixture
-def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
-    """Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
+def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes]]:
+    r"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
 
     The captured output is made available via ``capfd.readouterr()`` method
     calls, which return a ``(out, err)`` namedtuple.
     ``out`` and ``err`` will be ``byte`` objects.
+
+    Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
+
+    Example:
+
+    .. code-block:: python
+
+        def test_system_echo(capfdbinary):
+            os.system('echo "hello"')
+            captured = capfdbinary.readouterr()
+            assert captured.out == b"hello\n"
+
     """
-    capman = request.config.pluginmanager.getplugin("capturemanager")
-    capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request, _ispytest=True)
+    capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager")
+    capture_fixture = CaptureFixture(FDCaptureBinary, request, _ispytest=True)
     capman.set_fixture(capture_fixture)
     capture_fixture._start()
     yield capture_fixture
diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py
index 7703dee8c5a..2cbb17eca38 100644
--- a/src/_pytest/compat.py
+++ b/src/_pytest/compat.py
@@ -1,32 +1,23 @@
+# mypy: allow-untyped-defs
 """Python version compatibility code."""
+
+from __future__ import annotations
+
+from collections.abc import Callable
 import enum
 import functools
 import inspect
-import os
-import sys
-from contextlib import contextmanager
 from inspect import Parameter
 from inspect import signature
+import os
 from pathlib import Path
+import sys
 from typing import Any
-from typing import Callable
-from typing import Generic
-from typing import Optional
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-import attr
-import py
-
-if TYPE_CHECKING:
-    from typing import NoReturn
-    from typing_extensions import Final
+from typing import Final
+from typing import NoReturn
 
+import py
 
-_T = TypeVar("_T")
-_S = TypeVar("_S")
 
 #: constant to prepare valuing pylib path replacements/lazy proxies later on
 #  intended for removal in pytest 8.0 or 9.0
@@ -37,7 +28,7 @@
 # fmt: on
 
 
-def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH:
+def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH:
     """Internal wrapper to prepare lazy proxies for legacy_path instances"""
     return LEGACY_PATH(path)
 
@@ -47,30 +38,16 @@ def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH:
 # https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions
 class NotSetType(enum.Enum):
     token = 0
-NOTSET: "Final" = NotSetType.token  # noqa: E305
+NOTSET: Final = NotSetType.token
 # fmt: on
 
-if sys.version_info >= (3, 8):
-    from importlib import metadata as importlib_metadata
-else:
-    import importlib_metadata  # noqa: F401
-
-
-def _format_args(func: Callable[..., Any]) -> str:
-    return str(signature(func))
-
-
-def is_generator(func: object) -> bool:
-    genfunc = inspect.isgeneratorfunction(func)
-    return genfunc and not iscoroutinefunction(func)
-
 
 def iscoroutinefunction(func: object) -> bool:
     """Return True if func is a coroutine function (a function defined with async
     def syntax, and doesn't contain yield), or a function decorated with
     @asyncio.coroutine.
 
-    Note: copied and modified from Python 3.5's builtin couroutines.py to avoid
+    Note: copied and modified from Python 3.5's builtin coroutines.py to avoid
     importing asyncio directly, which in turns also initializes the "logging"
     module as a side-effect (see issue #8).
     """
@@ -83,7 +60,7 @@ def is_async_function(func: object) -> bool:
     return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
 
 
-def getlocation(function, curdir: Optional[str] = None) -> str:
+def getlocation(function, curdir: str | os.PathLike[str] | None = None) -> str:
     function = get_real_func(function)
     fn = Path(inspect.getfile(function))
     lineno = function.__code__.co_firstlineno
@@ -93,8 +70,8 @@ def getlocation(function, curdir: Optional[str] = None) -> str:
         except ValueError:
             pass
         else:
-            return "%s:%d" % (relfn, lineno + 1)
-    return "%s:%d" % (fn, lineno + 1)
+            return f"{relfn}:{lineno + 1}"
+    return f"{fn}:{lineno + 1}"
 
 
 def num_mock_patch_args(function) -> int:
@@ -117,12 +94,11 @@ def num_mock_patch_args(function) -> int:
 
 
 def getfuncargnames(
-    function: Callable[..., Any],
+    function: Callable[..., object],
     *,
     name: str = "",
-    is_method: bool = False,
-    cls: Optional[type] = None,
-) -> Tuple[str, ...]:
+    cls: type | None = None,
+) -> tuple[str, ...]:
     """Return the names of a function's mandatory arguments.
 
     Should return the names of all function arguments that:
@@ -131,9 +107,8 @@ def getfuncargnames(
     * Aren't bound with functools.partial.
     * Aren't replaced with mocks.
 
-    The is_method and cls arguments indicate that the function should
-    be treated as a bound method even though it's not unless, only in
-    the case of cls, the function is a static method.
+    The cls arguments indicate that the function should be treated as a bound
+    method even though it's not unless the function is a static method.
 
     The name parameter should be the original name in which the function was collected.
     """
@@ -171,7 +146,7 @@ def getfuncargnames(
     # If this function should be treated as a bound method even though
     # it's passed as an unbound method or function, remove the first
     # parameter name.
-    if is_method or (
+    if (
         # Not using `getattr` because we don't want to resolve the staticmethod.
         # Not using `cls.__dict__` because we want to check the entire MRO.
         cls
@@ -186,18 +161,7 @@ def getfuncargnames(
     return arg_names
 
 
-if sys.version_info < (3, 7):
-
-    @contextmanager
-    def nullcontext():
-        yield
-
-
-else:
-    from contextlib import nullcontext as nullcontext  # noqa: F401
-
-
-def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
+def get_default_arg_names(function: Callable[..., Any]) -> tuple[str, ...]:
     # Note: this code intentionally mirrors the code at the beginning of
     # getfuncargnames, to get the arguments which were excluded from its result
     # because they had default values.
@@ -217,25 +181,13 @@ def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
 )
 
 
-def _translate_non_printable(s: str) -> str:
-    return s.translate(_non_printable_ascii_translate_table)
-
-
-STRING_TYPES = bytes, str
-
-
-def _bytes_to_ascii(val: bytes) -> str:
-    return val.decode("ascii", "backslashreplace")
-
-
-def ascii_escaped(val: Union[bytes, str]) -> str:
+def ascii_escaped(val: bytes | str) -> str:
     r"""If val is pure ASCII, return it as an str, otherwise, escape
     bytes objects into a sequence of escaped bytes:
 
     b'\xc3\xb4\xc5\xd6' -> r'\xc3\xb4\xc5\xd6'
 
-    and escapes unicode objects into a sequence of escaped unicode
-    ids, e.g.:
+    and escapes strings into a sequence of escaped unicode ids, e.g.:
 
     r'4\nV\U00043efa\x0eMXWB\x1e\u3028\u15fd\xcd\U0007d944'
 
@@ -246,67 +198,22 @@ def ascii_escaped(val: Union[bytes, str]) -> str:
        a UTF-8 string.
     """
     if isinstance(val, bytes):
-        ret = _bytes_to_ascii(val)
+        ret = val.decode("ascii", "backslashreplace")
     else:
         ret = val.encode("unicode_escape").decode("ascii")
-    return _translate_non_printable(ret)
-
-
-@attr.s
-class _PytestWrapper:
-    """Dummy wrapper around a function object for internal use only.
-
-    Used to correctly unwrap the underlying function object when we are
-    creating fixtures, because we wrap the function object ourselves with a
-    decorator to issue warnings when the fixture function is called directly.
-    """
-
-    obj = attr.ib()
+    return ret.translate(_non_printable_ascii_translate_table)
 
 
 def get_real_func(obj):
     """Get the real function object of the (possibly) wrapped object by
-    functools.wraps or functools.partial."""
-    start_obj = obj
-    for i in range(100):
-        # __pytest_wrapped__ is set by @pytest.fixture when wrapping the fixture function
-        # to trigger a warning if it gets called directly instead of by pytest: we don't
-        # want to unwrap further than this otherwise we lose useful wrappings like @mock.patch (#3774)
-        new_obj = getattr(obj, "__pytest_wrapped__", None)
-        if isinstance(new_obj, _PytestWrapper):
-            obj = new_obj.obj
-            break
-        new_obj = getattr(obj, "__wrapped__", None)
-        if new_obj is None:
-            break
-        obj = new_obj
-    else:
-        from _pytest._io.saferepr import saferepr
+    :func:`functools.wraps`, or :func:`functools.partial`."""
+    obj = inspect.unwrap(obj)
 
-        raise ValueError(
-            ("could not find real function of {start}\nstopped at {current}").format(
-                start=saferepr(start_obj), current=saferepr(obj)
-            )
-        )
     if isinstance(obj, functools.partial):
         obj = obj.func
     return obj
 
 
-def get_real_method(obj, holder):
-    """Attempt to obtain the real function object that might be wrapping
-    ``obj``, while at the same time returning a bound method to ``holder`` if
-    the original object was a bound method."""
-    try:
-        is_method = hasattr(obj, "__func__")
-        obj = get_real_func(obj)
-    except Exception:  # pragma: no cover
-        return obj
-    if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
-        obj = obj.__get__(holder)
-    return obj
-
-
 def getimfunc(func):
     try:
         return func.__func__
@@ -339,47 +246,25 @@ def safe_isclass(obj: object) -> bool:
         return False
 
 
-if TYPE_CHECKING:
-    if sys.version_info >= (3, 8):
-        from typing import final as final
-    else:
-        from typing_extensions import final as final
-elif sys.version_info >= (3, 8):
-    from typing import final as final
-else:
-
-    def final(f):
-        return f
-
-
-if sys.version_info >= (3, 8):
-    from functools import cached_property as cached_property
-else:
-    from typing import overload
-    from typing import Type
-
-    class cached_property(Generic[_S, _T]):
-        __slots__ = ("func", "__doc__")
+def get_user_id() -> int | None:
+    """Return the current process's real user id or None if it could not be
+    determined.
 
-        def __init__(self, func: Callable[[_S], _T]) -> None:
-            self.func = func
-            self.__doc__ = func.__doc__
-
-        @overload
-        def __get__(
-            self, instance: None, owner: Optional[Type[_S]] = ...
-        ) -> "cached_property[_S, _T]":
-            ...
-
-        @overload
-        def __get__(self, instance: _S, owner: Optional[Type[_S]] = ...) -> _T:
-            ...
-
-        def __get__(self, instance, owner=None):
-            if instance is None:
-                return self
-            value = instance.__dict__[self.func.__name__] = self.func(instance)
-            return value
+    :return: The user id or None if it could not be determined.
+    """
+    # mypy follows the version and platform checking expectation of PEP 484:
+    # https://mypy.readthedocs.io/en/stable/common_issues.html?highlight=platform#python-version-and-system-platform-checks
+    # Containment checks are too complex for mypy v1.5.0 and cause failure.
+    if sys.platform == "win32" or sys.platform == "emscripten":
+        # win32 does not have a getuid() function.
+        # Emscripten has a return 0 stub.
+        return None
+    else:
+        # On other platforms, a return value of -1 is assumed to indicate that
+        # the current process's real user id could not be determined.
+        ERROR = -1
+        uid = os.getuid()
+        return uid if uid != ERROR else None
 
 
 # Perform exhaustiveness checking.
@@ -413,5 +298,5 @@ def __get__(self, instance, owner=None):
 # previously.
 #
 # This also work for Enums (if you use `is` to compare) and Literals.
-def assert_never(value: "NoReturn") -> "NoReturn":
+def assert_never(value: NoReturn) -> NoReturn:
     assert False, f"Unhandled value: {value} ({type(value).__name__})"
diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py
index eadce78fa65..56b04719641 100644
--- a/src/_pytest/config/__init__.py
+++ b/src/_pytest/config/__init__.py
@@ -1,54 +1,61 @@
+# mypy: allow-untyped-defs
 """Command line options, ini-file and conftest.py processing."""
+
+from __future__ import annotations
+
 import argparse
 import collections.abc
+from collections.abc import Callable
+from collections.abc import Generator
+from collections.abc import Iterable
+from collections.abc import Iterator
+from collections.abc import Sequence
 import contextlib
 import copy
+import dataclasses
 import enum
+from functools import lru_cache
+import glob
+import importlib.metadata
 import inspect
 import os
+import pathlib
 import re
 import shlex
 import sys
-import types
-import warnings
-from functools import lru_cache
-from pathlib import Path
 from textwrap import dedent
-from types import TracebackType
+import types
+from types import FunctionType
 from typing import Any
-from typing import Callable
 from typing import cast
-from typing import Dict
-from typing import Generator
+from typing import Final
+from typing import final
 from typing import IO
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Set
 from typing import TextIO
-from typing import Tuple
-from typing import Type
 from typing import TYPE_CHECKING
-from typing import Union
+import warnings
 
-import attr
+import pluggy
 from pluggy import HookimplMarker
+from pluggy import HookimplOpts
 from pluggy import HookspecMarker
+from pluggy import HookspecOpts
 from pluggy import PluginManager
 
-import _pytest._code
-import _pytest.deprecated
-import _pytest.hookspec
+from .compat import PathAwareHookProxy
 from .exceptions import PrintHelp as PrintHelp
 from .exceptions import UsageError as UsageError
 from .findpaths import determine_setup
+from _pytest import __version__
+import _pytest._code
 from _pytest._code import ExceptionInfo
 from _pytest._code import filter_traceback
+from _pytest._code.code import TracebackStyle
 from _pytest._io import TerminalWriter
-from _pytest.compat import final
-from _pytest.compat import importlib_metadata
+from _pytest.config.argparsing import Argument
+from _pytest.config.argparsing import Parser
+import _pytest.deprecated
+import _pytest.hookspec
 from _pytest.outcomes import fail
 from _pytest.outcomes import Skipped
 from _pytest.pathlib import absolutepath
@@ -56,15 +63,16 @@
 from _pytest.pathlib import import_path
 from _pytest.pathlib import ImportMode
 from _pytest.pathlib import resolve_package_path
+from _pytest.pathlib import safe_exists
 from _pytest.stash import Stash
 from _pytest.warning_types import PytestConfigWarning
+from _pytest.warning_types import warn_explicit_for
 
-if TYPE_CHECKING:
 
-    from _pytest._code.code import _TracebackStyle
+if TYPE_CHECKING:
+    from _pytest.assertions.rewrite import AssertionRewritingHook
+    from _pytest.cacheprovider import Cache
     from _pytest.terminal import TerminalReporter
-    from .argparsing import Argument
-
 
 _PluggyPlugin = object
 """A type to represent plugin objects.
@@ -106,17 +114,15 @@ class ExitCode(enum.IntEnum):
 class ConftestImportFailure(Exception):
     def __init__(
         self,
-        path: Path,
-        excinfo: Tuple[Type[Exception], Exception, TracebackType],
+        path: pathlib.Path,
+        *,
+        cause: Exception,
     ) -> None:
-        super().__init__(path, excinfo)
         self.path = path
-        self.excinfo = excinfo
+        self.cause = cause
 
     def __str__(self) -> str:
-        return "{}: {} (from {})".format(
-            self.excinfo[0].__name__, self.excinfo[1], self.path
-        )
+        return f"{type(self.cause).__name__}: {self.cause} (from {self.path})"
 
 
 def filter_traceback_for_conftest_import_failure(
@@ -131,21 +137,25 @@ def filter_traceback_for_conftest_import_failure(
 
 
 def main(
-    args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
-    plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
-) -> Union[int, ExitCode]:
+    args: list[str] | os.PathLike[str] | None = None,
+    plugins: Sequence[str | _PluggyPlugin] | None = None,
+) -> int | ExitCode:
     """Perform an in-process test run.
 
-    :param args: List of command line arguments.
+    :param args:
+        List of command line arguments. If `None` or not given, defaults to reading
+        arguments directly from the process command line (:data:`sys.argv`).
     :param plugins: List of plugin objects to be auto-registered during initialization.
 
     :returns: An exit code.
     """
+    old_pytest_version = os.environ.get("PYTEST_VERSION")
     try:
+        os.environ["PYTEST_VERSION"] = __version__
         try:
             config = _prepareconfig(args, plugins)
         except ConftestImportFailure as e:
-            exc_info = ExceptionInfo.from_exc_info(e.excinfo)
+            exc_info = ExceptionInfo.from_exception(e.cause)
             tw = TerminalWriter(sys.stderr)
             tw.line(f"ImportError while loading conftest '{e.path}'.", red=True)
             exc_info.traceback = exc_info.traceback.filter(
@@ -162,9 +172,7 @@ def main(
             return ExitCode.USAGE_ERROR
         else:
             try:
-                ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main(
-                    config=config
-                )
+                ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config)
                 try:
                     return ExitCode(ret)
                 except ValueError:
@@ -176,6 +184,11 @@ def main(
         for msg in e.args:
             tw.line(f"ERROR: {msg}\n", red=True)
         return ExitCode.USAGE_ERROR
+    finally:
+        if old_pytest_version is None:
+            os.environ.pop("PYTEST_VERSION", None)
+        else:
+            os.environ["PYTEST_VERSION"] = old_pytest_version
 
 
 def console_main() -> int:
@@ -231,7 +244,8 @@ def directory_arg(path: str, optname: str) -> str:
     "helpconfig",  # Provides -p.
 )
 
-default_plugins = essential_plugins + (
+default_plugins = (
+    *essential_plugins,
     "python",
     "terminal",
     "debugging",
@@ -243,7 +257,6 @@ def directory_arg(path: str, optname: str) -> str:
     "monkeypatch",
     "recwarn",
     "pastebin",
-    "nose",
     "assertion",
     "junitxml",
     "doctest",
@@ -252,11 +265,11 @@ def directory_arg(path: str, optname: str) -> str:
     "setuponly",
     "setupplan",
     "stepwise",
+    "unraisableexception",
+    "threadexception",
     "warnings",
     "logging",
     "reports",
-    "pythonpath",
-    *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []),
     "faulthandler",
 )
 
@@ -266,9 +279,9 @@ def directory_arg(path: str, optname: str) -> str:
 
 
 def get_config(
-    args: Optional[List[str]] = None,
-    plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
-) -> "Config":
+    args: list[str] | None = None,
+    plugins: Sequence[str | _PluggyPlugin] | None = None,
+) -> Config:
     # subsequent calls to main will create a fresh instance
     pluginmanager = PytestPluginManager()
     config = Config(
@@ -276,7 +289,7 @@ def get_config(
         invocation_params=Config.InvocationParams(
             args=args or (),
             plugins=plugins,
-            dir=Path.cwd(),
+            dir=pathlib.Path.cwd(),
         ),
     )
 
@@ -290,7 +303,7 @@ def get_config(
     return config
 
 
-def get_plugin_manager() -> "PytestPluginManager":
+def get_plugin_manager() -> PytestPluginManager:
     """Obtain a new instance of the
     :py:class:`pytest.PytestPluginManager`, with default plugins
     already loaded.
@@ -302,15 +315,17 @@ def get_plugin_manager() -> "PytestPluginManager":
 
 
 def _prepareconfig(
-    args: Optional[Union[List[str], "os.PathLike[str]"]] = None,
-    plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None,
-) -> "Config":
+    args: list[str] | os.PathLike[str] | None = None,
+    plugins: Sequence[str | _PluggyPlugin] | None = None,
+) -> Config:
     if args is None:
         args = sys.argv[1:]
     elif isinstance(args, os.PathLike):
         args = [os.fspath(args)]
     elif not isinstance(args, list):
-        msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})"
+        msg = (  # type:ignore[unreachable]
+            "`args` parameter expected to be a list of strings, got: {!r} (type: {})"
+        )
         raise TypeError(msg.format(args, type(args)))
 
     config = get_config(args, plugins)
@@ -331,6 +346,46 @@ def _prepareconfig(
         raise
 
 
+def _get_directory(path: pathlib.Path) -> pathlib.Path:
+    """Get the directory of a path - itself if already a directory."""
+    if path.is_file():
+        return path.parent
+    else:
+        return path
+
+
+def _get_legacy_hook_marks(
+    method: Any,
+    hook_type: str,
+    opt_names: tuple[str, ...],
+) -> dict[str, bool]:
+    if TYPE_CHECKING:
+        # abuse typeguard from importlib to avoid massive method type union that's lacking an alias
+        assert inspect.isroutine(method)
+    known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])}
+    must_warn: list[str] = []
+    opts: dict[str, bool] = {}
+    for opt_name in opt_names:
+        opt_attr = getattr(method, opt_name, AttributeError)
+        if opt_attr is not AttributeError:
+            must_warn.append(f"{opt_name}={opt_attr}")
+            opts[opt_name] = True
+        elif opt_name in known_marks:
+            must_warn.append(f"{opt_name}=True")
+            opts[opt_name] = True
+        else:
+            opts[opt_name] = False
+    if must_warn:
+        hook_opts = ", ".join(must_warn)
+        message = _pytest.deprecated.HOOK_LEGACY_MARKING.format(
+            type=hook_type,
+            fullname=method.__qualname__,
+            hook_opts=hook_opts,
+        )
+        warn_explicit_for(cast(FunctionType, method), message)
+    return opts
+
+
 @final
 class PytestPluginManager(PluginManager):
     """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
@@ -345,22 +400,30 @@ def __init__(self) -> None:
         import _pytest.assertion
 
         super().__init__("pytest")
-        # The objects are module objects, only used generically.
-        self._conftest_plugins: Set[types.ModuleType] = set()
 
-        # State related to local conftest plugins.
-        self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {}
-        self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
-        self._confcutdir: Optional[Path] = None
+        # -- State related to local conftest plugins.
+        # All loaded conftest modules.
+        self._conftest_plugins: set[types.ModuleType] = set()
+        # All conftest modules applicable for a directory.
+        # This includes the directory's own conftest modules as well
+        # as those of its parent directories.
+        self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {}
+        # Cutoff directory above which conftests are no longer discovered.
+        self._confcutdir: pathlib.Path | None = None
+        # If set, conftest loading is skipped.
         self._noconftest = False
-        self._duplicatepaths: Set[Path] = set()
+
+        # _getconftestmodules()'s call to _get_directory() causes a stat
+        # storm when it's called potentially thousands of times in a test
+        # session (#9478), often with the same path, so cache it.
+        self._get_directory = lru_cache(256)(_get_directory)
 
         # plugins that were explicitly skipped with pytest.skip
         # list of (module name, skip reason)
         # previously we would issue a warning when a plugin was skipped, but
         # since we refactored warnings as first citizens of Config, they are
         # just stored here to be used later.
-        self.skipped_plugins: List[Tuple[str, str]] = []
+        self.skipped_plugins: list[tuple[str, str]] = []
 
         self.add_hookspecs(_pytest.hookspec)
         self.register(self)
@@ -384,55 +447,46 @@ def __init__(self) -> None:
         # Used to know when we are importing conftests after the pytest_configure stage.
         self._configured = False
 
-    def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str):
+    def parse_hookimpl_opts(
+        self, plugin: _PluggyPlugin, name: str
+    ) -> HookimplOpts | None:
+        """:meta private:"""
         # pytest hooks are always prefixed with "pytest_",
         # so we avoid accessing possibly non-readable attributes
         # (see issue #1073).
         if not name.startswith("pytest_"):
-            return
-        # Ignore names which can not be hooks.
+            return None
+        # Ignore names which cannot be hooks.
         if name == "pytest_plugins":
-            return
+            return None
 
-        method = getattr(plugin, name)
         opts = super().parse_hookimpl_opts(plugin, name)
+        if opts is not None:
+            return opts
 
+        method = getattr(plugin, name)
         # Consider only actual functions for hooks (#3775).
         if not inspect.isroutine(method):
-            return
-
+            return None
         # Collect unmarked hooks as long as they have the `pytest_' prefix.
-        if opts is None and name.startswith("pytest_"):
-            opts = {}
-        if opts is not None:
-            # TODO: DeprecationWarning, people should use hookimpl
-            # https://github.com/pytest-dev/pytest/issues/4562
-            known_marks = {m.name for m in getattr(method, "pytestmark", [])}
-
-            for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
-                opts.setdefault(name, hasattr(method, name) or name in known_marks)
-        return opts
+        return _get_legacy_hook_marks(  # type: ignore[return-value]
+            method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
+        )
 
-    def parse_hookspec_opts(self, module_or_class, name: str):
+    def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None:
+        """:meta private:"""
         opts = super().parse_hookspec_opts(module_or_class, name)
         if opts is None:
             method = getattr(module_or_class, name)
-
             if name.startswith("pytest_"):
-                # todo: deprecate hookspec hacks
-                # https://github.com/pytest-dev/pytest/issues/4562
-                known_marks = {m.name for m in getattr(method, "pytestmark", [])}
-                opts = {
-                    "firstresult": hasattr(method, "firstresult")
-                    or "firstresult" in known_marks,
-                    "historic": hasattr(method, "historic")
-                    or "historic" in known_marks,
-                }
+                opts = _get_legacy_hook_marks(  # type: ignore[assignment]
+                    method,
+                    "spec",
+                    ("firstresult", "historic"),
+                )
         return opts
 
-    def register(
-        self, plugin: _PluggyPlugin, name: Optional[str] = None
-    ) -> Optional[str]:
+    def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None:
         if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS:
             warnings.warn(
                 PytestConfigWarning(
@@ -443,38 +497,44 @@ def register(
                 )
             )
             return None
-        ret: Optional[str] = super().register(plugin, name)
-        if ret:
+        plugin_name = super().register(plugin, name)
+        if plugin_name is not None:
             self.hook.pytest_plugin_registered.call_historic(
-                kwargs=dict(plugin=plugin, manager=self)
+                kwargs=dict(
+                    plugin=plugin,
+                    plugin_name=plugin_name,
+                    manager=self,
+                )
             )
 
             if isinstance(plugin, types.ModuleType):
                 self.consider_module(plugin)
-        return ret
+        return plugin_name
 
     def getplugin(self, name: str):
         # Support deprecated naming because plugins (xdist e.g.) use it.
-        plugin: Optional[_PluggyPlugin] = self.get_plugin(name)
+        plugin: _PluggyPlugin | None = self.get_plugin(name)
         return plugin
 
     def hasplugin(self, name: str) -> bool:
         """Return whether a plugin with the given name is registered."""
         return bool(self.get_plugin(name))
 
-    def pytest_configure(self, config: "Config") -> None:
+    def pytest_configure(self, config: Config) -> None:
         """:meta private:"""
         # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
         # we should remove tryfirst/trylast as markers.
         config.addinivalue_line(
             "markers",
             "tryfirst: mark a hook implementation function such that the "
-            "plugin machinery will try to call it first/as early as possible.",
+            "plugin machinery will try to call it first/as early as possible. "
+            "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.",
         )
         config.addinivalue_line(
             "markers",
             "trylast: mark a hook implementation function such that the "
-            "plugin machinery will try to call it last/as late as possible.",
+            "plugin machinery will try to call it last/as late as possible. "
+            "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.",
         )
         self._configured = True
 
@@ -482,7 +542,16 @@ def pytest_configure(self, config: "Config") -> None:
     # Internal API for local conftest plugin handling.
     #
     def _set_initial_conftests(
-        self, namespace: argparse.Namespace, rootpath: Path
+        self,
+        args: Sequence[str | pathlib.Path],
+        pyargs: bool,
+        noconftest: bool,
+        rootpath: pathlib.Path,
+        confcutdir: pathlib.Path | None,
+        invocation_dir: pathlib.Path,
+        importmode: ImportMode | str,
+        *,
+        consider_namespace_packages: bool,
     ) -> None:
         """Load initial conftest files given a preparsed "namespace".
 
@@ -491,79 +560,120 @@ def _set_initial_conftests(
         All builtin and 3rd party plugins will have been loaded, however, so
         common options will not confuse our logic here.
         """
-        current = Path.cwd()
         self._confcutdir = (
-            absolutepath(current / namespace.confcutdir)
-            if namespace.confcutdir
-            else None
+            absolutepath(invocation_dir / confcutdir) if confcutdir else None
         )
-        self._noconftest = namespace.noconftest
-        self._using_pyargs = namespace.pyargs
-        testpaths = namespace.file_or_dir
+        self._noconftest = noconftest
+        self._using_pyargs = pyargs
         foundanchor = False
-        for testpath in testpaths:
-            path = str(testpath)
+        for initial_path in args:
+            path = str(initial_path)
             # remove node-id syntax
             i = path.find("::")
             if i != -1:
                 path = path[:i]
-            anchor = absolutepath(current / path)
-            if anchor.exists():  # we found some file object
-                self._try_load_conftest(anchor, namespace.importmode, rootpath)
+            anchor = absolutepath(invocation_dir / path)
+
+            # Ensure we do not break if what appears to be an anchor
+            # is in fact a very long option (#10169, #11394).
+            if safe_exists(anchor):
+                self._try_load_conftest(
+                    anchor,
+                    importmode,
+                    rootpath,
+                    consider_namespace_packages=consider_namespace_packages,
+                )
                 foundanchor = True
         if not foundanchor:
-            self._try_load_conftest(current, namespace.importmode, rootpath)
+            self._try_load_conftest(
+                invocation_dir,
+                importmode,
+                rootpath,
+                consider_namespace_packages=consider_namespace_packages,
+            )
+
+    def _is_in_confcutdir(self, path: pathlib.Path) -> bool:
+        """Whether to consider the given path to load conftests from."""
+        if self._confcutdir is None:
+            return True
+        # The semantics here are literally:
+        #   Do not load a conftest if it is found upwards from confcut dir.
+        # But this is *not* the same as:
+        #   Load only conftests from confcutdir or below.
+        # At first glance they might seem the same thing, however we do support use cases where
+        # we want to load conftests that are not found in confcutdir or below, but are found
+        # in completely different directory hierarchies like packages installed
+        # in out-of-source trees.
+        # (see #9767 for a regression where the logic was inverted).
+        return path not in self._confcutdir.parents
 
     def _try_load_conftest(
-        self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
+        self,
+        anchor: pathlib.Path,
+        importmode: str | ImportMode,
+        rootpath: pathlib.Path,
+        *,
+        consider_namespace_packages: bool,
     ) -> None:
-        self._getconftestmodules(anchor, importmode, rootpath)
+        self._loadconftestmodules(
+            anchor,
+            importmode,
+            rootpath,
+            consider_namespace_packages=consider_namespace_packages,
+        )
         # let's also consider test* subdirs
         if anchor.is_dir():
             for x in anchor.glob("test*"):
                 if x.is_dir():
-                    self._getconftestmodules(x, importmode, rootpath)
+                    self._loadconftestmodules(
+                        x,
+                        importmode,
+                        rootpath,
+                        consider_namespace_packages=consider_namespace_packages,
+                    )
 
-    def _getconftestmodules(
-        self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
-    ) -> List[types.ModuleType]:
+    def _loadconftestmodules(
+        self,
+        path: pathlib.Path,
+        importmode: str | ImportMode,
+        rootpath: pathlib.Path,
+        *,
+        consider_namespace_packages: bool,
+    ) -> None:
         if self._noconftest:
-            return []
+            return
 
-        if path.is_file():
-            directory = path.parent
-        else:
-            directory = path
+        directory = self._get_directory(path)
 
         # Optimization: avoid repeated searches in the same directory.
         # Assumes always called with same importmode and rootpath.
-        existing_clist = self._dirpath2confmods.get(directory)
-        if existing_clist:
-            return existing_clist
+        if directory in self._dirpath2confmods:
+            return
 
-        # XXX these days we may rather want to use config.rootpath
-        # and allow users to opt into looking into the rootdir parent
-        # directories instead of requiring to specify confcutdir.
         clist = []
-        confcutdir_parents = self._confcutdir.parents if self._confcutdir else []
         for parent in reversed((directory, *directory.parents)):
-            if parent in confcutdir_parents:
-                continue
-            conftestpath = parent / "conftest.py"
-            if conftestpath.is_file():
-                mod = self._importconftest(conftestpath, importmode, rootpath)
-                clist.append(mod)
+            if self._is_in_confcutdir(parent):
+                conftestpath = parent / "conftest.py"
+                if conftestpath.is_file():
+                    mod = self._importconftest(
+                        conftestpath,
+                        importmode,
+                        rootpath,
+                        consider_namespace_packages=consider_namespace_packages,
+                    )
+                    clist.append(mod)
         self._dirpath2confmods[directory] = clist
-        return clist
+
+    def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]:
+        directory = self._get_directory(path)
+        return self._dirpath2confmods.get(directory, ())
 
     def _rget_with_confmod(
         self,
         name: str,
-        path: Path,
-        importmode: Union[str, ImportMode],
-        rootpath: Path,
-    ) -> Tuple[types.ModuleType, Any]:
-        modules = self._getconftestmodules(path, importmode, rootpath=rootpath)
+        path: pathlib.Path,
+    ) -> tuple[types.ModuleType, Any]:
+        modules = self._getconftestmodules(path)
         for mod in reversed(modules):
             try:
                 return mod, getattr(mod, name)
@@ -572,47 +682,62 @@ def _rget_with_confmod(
         raise KeyError(name)
 
     def _importconftest(
-        self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path
+        self,
+        conftestpath: pathlib.Path,
+        importmode: str | ImportMode,
+        rootpath: pathlib.Path,
+        *,
+        consider_namespace_packages: bool,
     ) -> types.ModuleType:
-        # Use a resolved Path object as key to avoid loading the same conftest
-        # twice with build systems that create build directories containing
-        # symlinks to actual files.
-        # Using Path().resolve() is better than py.path.realpath because
-        # it resolves to the correct path/drive in case-insensitive file systems (#5792)
-        key = conftestpath.resolve()
-
-        with contextlib.suppress(KeyError):
-            return self._conftestpath2mod[key]
-
+        conftestpath_plugin_name = str(conftestpath)
+        existing = self.get_plugin(conftestpath_plugin_name)
+        if existing is not None:
+            return cast(types.ModuleType, existing)
+
+        # conftest.py files there are not in a Python package all have module
+        # name "conftest", and thus conflict with each other. Clear the existing
+        # before loading the new one, otherwise the existing one will be
+        # returned from the module cache.
         pkgpath = resolve_package_path(conftestpath)
         if pkgpath is None:
-            _ensure_removed_sysmodule(conftestpath.stem)
+            try:
+                del sys.modules[conftestpath.stem]
+            except KeyError:
+                pass
 
         try:
-            mod = import_path(conftestpath, mode=importmode, root=rootpath)
+            mod = import_path(
+                conftestpath,
+                mode=importmode,
+                root=rootpath,
+                consider_namespace_packages=consider_namespace_packages,
+            )
         except Exception as e:
             assert e.__traceback__ is not None
-            exc_info = (type(e), e, e.__traceback__)
-            raise ConftestImportFailure(conftestpath, exc_info) from e
+            raise ConftestImportFailure(conftestpath, cause=e) from e
 
         self._check_non_top_pytest_plugins(mod, conftestpath)
 
         self._conftest_plugins.add(mod)
-        self._conftestpath2mod[key] = mod
         dirpath = conftestpath.parent
         if dirpath in self._dirpath2confmods:
             for path, mods in self._dirpath2confmods.items():
-                if path and dirpath in path.parents or path == dirpath:
-                    assert mod not in mods
+                if dirpath in path.parents or path == dirpath:
+                    if mod in mods:
+                        raise AssertionError(
+                            f"While trying to load conftest path {conftestpath!s}, "
+                            f"found that the module {mod} is already loaded with path {mod.__file__}. "
+                            "This is not supposed to happen. Please report this issue to pytest."
+                        )
                     mods.append(mod)
         self.trace(f"loading conftestmodule {mod!r}")
-        self.consider_conftest(mod)
+        self.consider_conftest(mod, registration_name=conftestpath_plugin_name)
         return mod
 
     def _check_non_top_pytest_plugins(
         self,
         mod: types.ModuleType,
-        conftestpath: Path,
+        conftestpath: pathlib.Path,
     ) -> None:
         if (
             hasattr(mod, "pytest_plugins")
@@ -655,6 +780,7 @@ def consider_preparse(
                     parg = opt[2:]
                 else:
                     continue
+                parg = parg.strip()
                 if exclude_only and not parg.startswith("no:"):
                     continue
                 self.consider_pluginarg(parg)
@@ -664,7 +790,7 @@ def consider_pluginarg(self, arg: str) -> None:
         if arg.startswith("no:"):
             name = arg[3:]
             if name in essential_plugins:
-                raise UsageError("plugin %s cannot be disabled" % name)
+                raise UsageError(f"plugin {name} cannot be disabled")
 
             # PR #4304: remove stepwise if cacheprovider is blocked.
             if name == "cacheprovider":
@@ -676,18 +802,17 @@ def consider_pluginarg(self, arg: str) -> None:
                 self.set_blocked("pytest_" + name)
         else:
             name = arg
-            # Unblock the plugin.  None indicates that it has been blocked.
-            # There is no interface with pluggy for this.
-            if self._name2plugin.get(name, -1) is None:
-                del self._name2plugin[name]
+            # Unblock the plugin.
+            self.unblock(name)
             if not name.startswith("pytest_"):
-                if self._name2plugin.get("pytest_" + name, -1) is None:
-                    del self._name2plugin["pytest_" + name]
+                self.unblock("pytest_" + name)
             self.import_plugin(arg, consider_entry_points=True)
 
-    def consider_conftest(self, conftestmodule: types.ModuleType) -> None:
+    def consider_conftest(
+        self, conftestmodule: types.ModuleType, registration_name: str
+    ) -> None:
         """:meta private:"""
-        self.register(conftestmodule, name=conftestmodule.__file__)
+        self.register(conftestmodule, name=registration_name)
 
     def consider_env(self) -> None:
         """:meta private:"""
@@ -698,7 +823,7 @@ def consider_module(self, mod: types.ModuleType) -> None:
         self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
 
     def _import_plugin_specs(
-        self, spec: Union[None, types.ModuleType, str, Sequence[str]]
+        self, spec: None | types.ModuleType | str | Sequence[str]
     ) -> None:
         plugins = _get_plugin_specs_as_list(spec)
         for import_spec in plugins:
@@ -715,7 +840,7 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No
         # basename for historic purposes but must be imported with the
         # _pytest prefix.
         assert isinstance(modname, str), (
-            "module name as text required, got %r" % modname
+            f"module name as text required, got {modname!r}"
         )
         if self.is_blocked(modname) or self.get_plugin(modname) is not None:
             return
@@ -743,8 +868,8 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No
 
 
 def _get_plugin_specs_as_list(
-    specs: Union[None, types.ModuleType, str, Sequence[str]]
-) -> List[str]:
+    specs: None | types.ModuleType | str | Sequence[str],
+) -> list[str]:
     """Parse a plugins specification into a list of plugin names."""
     # None means empty.
     if specs is None:
@@ -759,18 +884,10 @@ def _get_plugin_specs_as_list(
     if isinstance(specs, collections.abc.Sequence):
         return list(specs)
     raise UsageError(
-        "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r"
-        % specs
+        f"Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}"
     )
 
 
-def _ensure_removed_sysmodule(modname: str) -> None:
-    try:
-        del sys.modules[modname]
-    except KeyError:
-        pass
-
-
 class Notset:
     def __repr__(self):
         return "<NOTSET>"
@@ -819,7 +936,8 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
         if is_simple_module:
             module_name, _ = os.path.splitext(fn)
             # we ignore "setup.py" at the root of the distribution
-            if module_name != "setup":
+            # as well as editable installation finder modules made by setuptools
+            if module_name != "setup" and not module_name.startswith("__editable__"):
                 seen_some = True
                 yield module_name
         elif is_package:
@@ -843,10 +961,6 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
             yield from _iter_rewritable_modules(new_package_files)
 
 
-def _args_converter(args: Iterable[str]) -> Tuple[str, ...]:
-    return tuple(args)
-
-
 @final
 class Config:
     """Access to configuration values, pluginmanager and plugin hooks.
@@ -860,7 +974,7 @@ class Config:
     """
 
     @final
-    @attr.s(frozen=True, auto_attribs=True)
+    @dataclasses.dataclass(frozen=True)
     class InvocationParams:
         """Holds parameters passed during :func:`pytest.main`.
 
@@ -876,24 +990,53 @@ class InvocationParams:
             Plugins accessing ``InvocationParams`` must be aware of that.
         """
 
-        args: Tuple[str, ...] = attr.ib(converter=_args_converter)
+        args: tuple[str, ...]
         """The command-line arguments as passed to :func:`pytest.main`."""
-        plugins: Optional[Sequence[Union[str, _PluggyPlugin]]]
+        plugins: Sequence[str | _PluggyPlugin] | None
         """Extra plugins, might be `None`."""
-        dir: Path
-        """The directory from which :func:`pytest.main` was invoked."""
+        dir: pathlib.Path
+        """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path"""
+
+        def __init__(
+            self,
+            *,
+            args: Iterable[str],
+            plugins: Sequence[str | _PluggyPlugin] | None,
+            dir: pathlib.Path,
+        ) -> None:
+            object.__setattr__(self, "args", tuple(args))
+            object.__setattr__(self, "plugins", plugins)
+            object.__setattr__(self, "dir", dir)
+
+    class ArgsSource(enum.Enum):
+        """Indicates the source of the test arguments.
+
+        .. versionadded:: 7.2
+        """
+
+        #: Command line arguments.
+        ARGS = enum.auto()
+        #: Invocation directory.
+        INVOCATION_DIR = enum.auto()
+        INCOVATION_DIR = INVOCATION_DIR  # backwards compatibility alias
+        #: 'testpaths' configuration value.
+        TESTPATHS = enum.auto()
+
+    # Set by cacheprovider plugin.
+    cache: Cache
 
     def __init__(
         self,
         pluginmanager: PytestPluginManager,
         *,
-        invocation_params: Optional[InvocationParams] = None,
+        invocation_params: InvocationParams | None = None,
     ) -> None:
-        from .argparsing import Parser, FILE_OR_DIR
+        from .argparsing import FILE_OR_DIR
+        from .argparsing import Parser
 
         if invocation_params is None:
             invocation_params = self.InvocationParams(
-                args=(), plugins=None, dir=Path.cwd()
+                args=(), plugins=None, dir=pathlib.Path.cwd()
             )
 
         self.option = argparse.Namespace()
@@ -929,27 +1072,22 @@ def __init__(
         # Deprecated alias. Was never public. Can be removed in a few releases.
         self._store = self.stash
 
-        from .compat import PathAwareHookProxy
-
         self.trace = self.pluginmanager.trace.root.get("config")
-        self.hook = PathAwareHookProxy(self.pluginmanager.hook)
-        self._inicache: Dict[str, Any] = {}
+        self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook)  # type: ignore[assignment]
+        self._inicache: dict[str, Any] = {}
         self._override_ini: Sequence[str] = ()
-        self._opt2dest: Dict[str, str] = {}
-        self._cleanup: List[Callable[[], None]] = []
+        self._opt2dest: dict[str, str] = {}
+        self._cleanup_stack = contextlib.ExitStack()
         self.pluginmanager.register(self, "pytestconfig")
         self._configured = False
         self.hook.pytest_addoption.call_historic(
             kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
         )
-
-        if TYPE_CHECKING:
-            from _pytest.cacheprovider import Cache
-
-            self.cache: Optional[Cache] = None
+        self.args_source = Config.ArgsSource.ARGS
+        self.args: list[str] = []
 
     @property
-    def rootpath(self) -> Path:
+    def rootpath(self) -> pathlib.Path:
         """The path to the :ref:`rootdir <rootdir>`.
 
         :type: pathlib.Path
@@ -959,49 +1097,51 @@ def rootpath(self) -> Path:
         return self._rootpath
 
     @property
-    def inipath(self) -> Optional[Path]:
+    def inipath(self) -> pathlib.Path | None:
         """The path to the :ref:`configfile <configfiles>`.
 
-        :type: Optional[pathlib.Path]
-
         .. versionadded:: 6.1
         """
         return self._inipath
 
     def add_cleanup(self, func: Callable[[], None]) -> None:
         """Add a function to be called when the config object gets out of
-        use (usually coninciding with pytest_unconfigure)."""
-        self._cleanup.append(func)
+        use (usually coinciding with pytest_unconfigure).
+        """
+        self._cleanup_stack.callback(func)
 
     def _do_configure(self) -> None:
         assert not self._configured
         self._configured = True
-        with warnings.catch_warnings():
-            warnings.simplefilter("default")
-            self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
+        self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
 
     def _ensure_unconfigure(self) -> None:
-        if self._configured:
-            self._configured = False
-            self.hook.pytest_unconfigure(config=self)
-            self.hook.pytest_configure._call_history = []
-        while self._cleanup:
-            fin = self._cleanup.pop()
-            fin()
+        try:
+            if self._configured:
+                self._configured = False
+                try:
+                    self.hook.pytest_unconfigure(config=self)
+                finally:
+                    self.hook.pytest_configure._call_history = []
+        finally:
+            try:
+                self._cleanup_stack.close()
+            finally:
+                self._cleanup_stack = contextlib.ExitStack()
 
     def get_terminal_writer(self) -> TerminalWriter:
-        terminalreporter: TerminalReporter = self.pluginmanager.get_plugin(
+        terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin(
             "terminalreporter"
         )
+        assert terminalreporter is not None
         return terminalreporter._tw
 
     def pytest_cmdline_parse(
-        self, pluginmanager: PytestPluginManager, args: List[str]
-    ) -> "Config":
+        self, pluginmanager: PytestPluginManager, args: list[str]
+    ) -> Config:
         try:
             self.parse(args)
         except UsageError:
-
             # Handle --version and --help here in a minimal fashion.
             # This gets done via helpconfig normally, but its
             # pytest_cmdline_main is not called in case of errors.
@@ -1024,10 +1164,10 @@ def pytest_cmdline_parse(
     def notify_exception(
         self,
         excinfo: ExceptionInfo[BaseException],
-        option: Optional[argparse.Namespace] = None,
+        option: argparse.Namespace | None = None,
     ) -> None:
         if option and getattr(option, "fulltrace", False):
-            style: _TracebackStyle = "long"
+            style: TracebackStyle = "long"
         else:
             style = "native"
         excrepr = excinfo.getrepr(
@@ -1036,18 +1176,22 @@ def notify_exception(
         res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
         if not any(res):
             for line in str(excrepr).split("\n"):
-                sys.stderr.write("INTERNALERROR> %s\n" % line)
+                sys.stderr.write(f"INTERNALERROR> {line}\n")
                 sys.stderr.flush()
 
     def cwd_relative_nodeid(self, nodeid: str) -> str:
         # nodeid's are relative to the rootpath, compute relative to cwd.
         if self.invocation_params.dir != self.rootpath:
-            fullpath = self.rootpath / nodeid
-            nodeid = bestrelpath(self.invocation_params.dir, fullpath)
+            base_path_part, *nodeid_part = nodeid.split("::")
+            # Only process path part
+            fullpath = self.rootpath / base_path_part
+            relative_path = bestrelpath(self.invocation_params.dir, fullpath)
+
+            nodeid = "::".join([relative_path, *nodeid_part])
         return nodeid
 
     @classmethod
-    def fromdictargs(cls, option_dict, args) -> "Config":
+    def fromdictargs(cls, option_dict, args) -> Config:
         """Constructor usable for subprocesses."""
         config = get_config(args)
         config.option.__dict__.update(option_dict)
@@ -1056,7 +1200,7 @@ def fromdictargs(cls, option_dict, args) -> "Config":
             config.pluginmanager.consider_pluginarg(x)
         return config
 
-    def _processopt(self, opt: "Argument") -> None:
+    def _processopt(self, opt: Argument) -> None:
         for name in opt._short_opts + opt._long_opts:
             self._opt2dest[name] = opt.dest
 
@@ -1065,9 +1209,30 @@ def _processopt(self, opt: "Argument") -> None:
                 setattr(self.option, opt.dest, opt.default)
 
     @hookimpl(trylast=True)
-    def pytest_load_initial_conftests(self, early_config: "Config") -> None:
+    def pytest_load_initial_conftests(self, early_config: Config) -> None:
+        # We haven't fully parsed the command line arguments yet, so
+        # early_config.args it not set yet. But we need it for
+        # discovering the initial conftests. So "pre-run" the logic here.
+        # It will be done for real in `parse()`.
+        args, args_source = early_config._decide_args(
+            args=early_config.known_args_namespace.file_or_dir,
+            pyargs=early_config.known_args_namespace.pyargs,
+            testpaths=early_config.getini("testpaths"),
+            invocation_dir=early_config.invocation_params.dir,
+            rootpath=early_config.rootpath,
+            warn=False,
+        )
         self.pluginmanager._set_initial_conftests(
-            early_config.known_args_namespace, rootpath=early_config.rootpath
+            args=args,
+            pyargs=early_config.known_args_namespace.pyargs,
+            noconftest=early_config.known_args_namespace.noconftest,
+            rootpath=early_config.rootpath,
+            confcutdir=early_config.known_args_namespace.confcutdir,
+            invocation_dir=early_config.invocation_params.dir,
+            importmode=early_config.known_args_namespace.importmode,
+            consider_namespace_packages=early_config.getini(
+                "consider_namespace_packages"
+            ),
         )
 
     def _initini(self, args: Sequence[str]) -> None:
@@ -1075,21 +1240,24 @@ def _initini(self, args: Sequence[str]) -> None:
             args, namespace=copy.copy(self.option)
         )
         rootpath, inipath, inicfg = determine_setup(
-            ns.inifilename,
-            ns.file_or_dir + unknown_args,
+            inifile=ns.inifilename,
+            args=ns.file_or_dir + unknown_args,
             rootdir_cmd_arg=ns.rootdir or None,
-            config=self,
+            invocation_dir=self.invocation_params.dir,
         )
         self._rootpath = rootpath
         self._inipath = inipath
         self.inicfg = inicfg
         self._parser.extra_info["rootdir"] = str(self.rootpath)
         self._parser.extra_info["inifile"] = str(self.inipath)
-        self._parser.addini("addopts", "extra command line options", "args")
-        self._parser.addini("minversion", "minimally required pytest version")
+        self._parser.addini("addopts", "Extra command line options", "args")
+        self._parser.addini("minversion", "Minimally required pytest version")
+        self._parser.addini(
+            "pythonpath", type="paths", help="Add paths to sys.path", default=[]
+        )
         self._parser.addini(
             "required_plugins",
-            "plugins that must be present for pytest to run",
+            "Plugins that must be present for pytest to run",
             type="args",
             default=[],
         )
@@ -1104,6 +1272,10 @@ def _consider_importhook(self, args: Sequence[str]) -> None:
         """
         ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
         mode = getattr(ns, "assertmode", "plain")
+
+        disable_autoload = getattr(ns, "disable_plugin_autoload", False) or bool(
+            os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
+        )
         if mode == "rewrite":
             import _pytest.assertion
 
@@ -1112,22 +1284,25 @@ def _consider_importhook(self, args: Sequence[str]) -> None:
             except SystemError:
                 mode = "plain"
             else:
-                self._mark_plugins_for_rewrite(hook)
+                self._mark_plugins_for_rewrite(hook, disable_autoload)
         self._warn_about_missing_assertion(mode)
 
-    def _mark_plugins_for_rewrite(self, hook) -> None:
+    def _mark_plugins_for_rewrite(
+        self, hook: AssertionRewritingHook, disable_autoload: bool
+    ) -> None:
         """Given an importhook, mark for rewrite any top-level
         modules or packages in the distribution package for
         all pytest plugins."""
         self.pluginmanager.rewrite_hook = hook
 
-        if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
-            # We don't autoload from setuptools entry points, no need to continue.
+        if disable_autoload:
+            # We don't autoload from distribution package entry points,
+            # no need to continue.
             return
 
         package_files = (
             str(file)
-            for dist in importlib_metadata.distributions()
+            for dist in importlib.metadata.distributions()
             if any(ep.group == "pytest11" for ep in dist.entry_points)
             for file in dist.files or []
         )
@@ -1135,7 +1310,19 @@ def _mark_plugins_for_rewrite(self, hook) -> None:
         for name in _iter_rewritable_modules(package_files):
             hook.mark_rewrite(name)
 
-    def _validate_args(self, args: List[str], via: str) -> List[str]:
+    def _configure_python_path(self) -> None:
+        # `pythonpath = a b` will set `sys.path` to `[a, b, x, y, z, ...]`
+        for path in reversed(self.getini("pythonpath")):
+            sys.path.insert(0, str(path))
+        self.add_cleanup(self._unconfigure_python_path)
+
+    def _unconfigure_python_path(self) -> None:
+        for path in self.getini("pythonpath"):
+            path_str = str(path)
+            if path_str in sys.path:
+                sys.path.remove(path_str)
+
+    def _validate_args(self, args: list[str], via: str) -> list[str]:
         """Validate known args."""
         self._parser._config_source_hint = via  # type: ignore
         try:
@@ -1147,7 +1334,52 @@ def _validate_args(self, args: List[str], via: str) -> List[str]:
 
         return args
 
-    def _preparse(self, args: List[str], addopts: bool = True) -> None:
+    def _decide_args(
+        self,
+        *,
+        args: list[str],
+        pyargs: bool,
+        testpaths: list[str],
+        invocation_dir: pathlib.Path,
+        rootpath: pathlib.Path,
+        warn: bool,
+    ) -> tuple[list[str], ArgsSource]:
+        """Decide the args (initial paths/nodeids) to use given the relevant inputs.
+
+        :param warn: Whether can issue warnings.
+
+        :returns: The args and the args source. Guaranteed to be non-empty.
+        """
+        if args:
+            source = Config.ArgsSource.ARGS
+            result = args
+        else:
+            if invocation_dir == rootpath:
+                source = Config.ArgsSource.TESTPATHS
+                if pyargs:
+                    result = testpaths
+                else:
+                    result = []
+                    for path in testpaths:
+                        result.extend(sorted(glob.iglob(path, recursive=True)))
+                    if testpaths and not result:
+                        if warn:
+                            warning_text = (
+                                "No files were found in testpaths; "
+                                "consider removing or adjusting your testpaths configuration. "
+                                "Searching recursively from the current directory instead."
+                            )
+                            self.issue_config_time_warning(
+                                PytestConfigWarning(warning_text), stacklevel=3
+                            )
+            else:
+                result = []
+            if not result:
+                source = Config.ArgsSource.INVOCATION_DIR
+                result = [str(invocation_dir)]
+        return result, source
+
+    def _preparse(self, args: list[str], addopts: bool = True) -> None:
         if addopts:
             env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
             if len(env_addopts):
@@ -1166,11 +1398,17 @@ def _preparse(self, args: List[str], addopts: bool = True) -> None:
         )
         self._checkversion()
         self._consider_importhook(args)
+        self._configure_python_path()
         self.pluginmanager.consider_preparse(args, exclude_only=False)
-        if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
-            # Don't autoload from setuptools entry point. Only explicitly specified
-            # plugins are going to be loaded.
+        if (
+            not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
+            and not self.known_args_namespace.disable_plugin_autoload
+        ):
+            # Autoloading from distribution package entry point has
+            # not been disabled.
             self.pluginmanager.load_setuptools_entrypoints("pytest11")
+        # Otherwise only plugins explicitly specified in PYTEST_PLUGINS
+        # are going to be loaded.
         self.pluginmanager.consider_env()
 
         self.known_args_namespace = self._parser.parse_known_args(
@@ -1180,13 +1418,11 @@ def _preparse(self, args: List[str], addopts: bool = True) -> None:
         self._validate_plugins()
         self._warn_about_skipped_plugins()
 
-        if self.known_args_namespace.strict:
-            self.issue_config_time_warning(
-                _pytest.deprecated.STRICT_OPTION, stacklevel=2
-            )
-
-        if self.known_args_namespace.confcutdir is None and self.inipath is not None:
-            confcutdir = str(self.inipath.parent)
+        if self.known_args_namespace.confcutdir is None:
+            if self.inipath is not None:
+                confcutdir = str(self.inipath.parent)
+            else:
+                confcutdir = str(self.rootpath)
             self.known_args_namespace.confcutdir = confcutdir
         try:
             self.hook.pytest_load_initial_conftests(
@@ -1195,7 +1431,7 @@ def _preparse(self, args: List[str], addopts: bool = True) -> None:
         except ConftestImportFailure as e:
             if self.known_args_namespace.help or self.known_args_namespace.version:
                 # we don't want to prevent --help/--version to work
-                # so just let is pass and print a warning at the end
+                # so just let it pass and print a warning at the end
                 self.issue_config_time_warning(
                     PytestConfigWarning(f"could not load initial conftests: {e.path}"),
                     stacklevel=2,
@@ -1203,12 +1439,14 @@ def _preparse(self, args: List[str], addopts: bool = True) -> None:
             else:
                 raise
 
-    @hookimpl(hookwrapper=True)
-    def pytest_collection(self) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_collection(self) -> Generator[None, object, object]:
         # Validate invalid ini keys after collection is done so we take in account
         # options added by late-loading conftest files.
-        yield
-        self._validate_config_options()
+        try:
+            return (yield)
+        finally:
+            self._validate_config_options()
 
     def _checkversion(self) -> None:
         import pytest
@@ -1220,17 +1458,12 @@ def _checkversion(self) -> None:
 
             if not isinstance(minver, str):
                 raise pytest.UsageError(
-                    "%s: 'minversion' must be a single value" % self.inipath
+                    f"{self.inipath}: 'minversion' must be a single value"
                 )
 
             if Version(minver) > Version(pytest.__version__):
                 raise pytest.UsageError(
-                    "%s: 'minversion' requires pytest-%s, actual pytest-%s'"
-                    % (
-                        self.inipath,
-                        minver,
-                        pytest.__version__,
-                    )
+                    f"{self.inipath}: 'minversion' requires pytest-{minver}, actual pytest-{pytest.__version__}'"
                 )
 
     def _validate_config_options(self) -> None:
@@ -1243,8 +1476,9 @@ def _validate_plugins(self) -> None:
             return
 
         # Imported lazily to improve start-up time.
+        from packaging.requirements import InvalidRequirement
+        from packaging.requirements import Requirement
         from packaging.version import Version
-        from packaging.requirements import InvalidRequirement, Requirement
 
         plugin_info = self.pluginmanager.list_plugin_distinfo()
         plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info}
@@ -1275,32 +1509,32 @@ def _warn_or_fail_if_strict(self, message: str) -> None:
 
         self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3)
 
-    def _get_unknown_ini_keys(self) -> List[str]:
+    def _get_unknown_ini_keys(self) -> list[str]:
         parser_inicfg = self._parser._inidict
         return [name for name in self.inicfg if name not in parser_inicfg]
 
-    def parse(self, args: List[str], addopts: bool = True) -> None:
+    def parse(self, args: list[str], addopts: bool = True) -> None:
         # Parse given cmdline arguments into this config object.
-        assert not hasattr(
-            self, "args"
-        ), "can only parse cmdline args at most once per Config object"
+        assert self.args == [], (
+            "can only parse cmdline args at most once per Config object"
+        )
         self.hook.pytest_addhooks.call_historic(
             kwargs=dict(pluginmanager=self.pluginmanager)
         )
         self._preparse(args, addopts=addopts)
-        # XXX deprecated hook:
-        self.hook.pytest_cmdline_preparse(config=self, args=args)
         self._parser.after_preparse = True  # type: ignore
         try:
             args = self._parser.parse_setoption(
                 args, self.option, namespace=self.option
             )
-            if not args:
-                if self.invocation_params.dir == self.rootpath:
-                    args = self.getini("testpaths")
-                if not args:
-                    args = [str(self.invocation_params.dir)]
-            self.args = args
+            self.args, self.args_source = self._decide_args(
+                args=args,
+                pyargs=self.known_args_namespace.pyargs,
+                testpaths=self.getini("testpaths"),
+                invocation_dir=self.invocation_params.dir,
+                rootpath=self.rootpath,
+                warn=True,
+            )
         except PrintHelp:
             pass
 
@@ -1308,7 +1542,7 @@ def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
         """Issue and handle a warning during the "configure" stage.
 
         During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item``
-        function because it is not possible to have hookwrappers around ``pytest_configure``.
+        function because it is not possible to have hook wrappers around ``pytest_configure``.
 
         This function is mainly intended for plugins that need to issue warnings during
         ``pytest_configure`` (or similar stages).
@@ -1330,14 +1564,6 @@ def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None:
         if records:
             frame = sys._getframe(stacklevel - 1)
             location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name
-            self.hook.pytest_warning_captured.call_historic(
-                kwargs=dict(
-                    warning_message=records[0],
-                    when="config",
-                    item=None,
-                    location=location,
-                )
-            )
             self.hook.pytest_warning_recorded.call_historic(
                 kwargs=dict(
                     warning_message=records[0],
@@ -1358,6 +1584,29 @@ def addinivalue_line(self, name: str, line: str) -> None:
     def getini(self, name: str):
         """Return configuration value from an :ref:`ini file <configfiles>`.
 
+        If a configuration value is not defined in an
+        :ref:`ini file <configfiles>`, then the ``default`` value provided while
+        registering the configuration through
+        :func:`parser.addini <pytest.Parser.addini>` will be returned.
+        Please note that you can even provide ``None`` as a valid
+        default value.
+
+        If ``default`` is not provided while registering using
+        :func:`parser.addini <pytest.Parser.addini>`, then a default value
+        based on the ``type`` parameter passed to
+        :func:`parser.addini <pytest.Parser.addini>` will be returned.
+        The default values based on ``type`` are:
+        ``paths``, ``pathlist``, ``args`` and ``linelist`` : empty list ``[]``
+        ``bool`` : ``False``
+        ``string`` : empty string ``""``
+        ``int`` : ``0``
+        ``float`` : ``0.0``
+
+        If neither the ``default`` nor the ``type`` parameter is passed
+        while registering the configuration through
+        :func:`parser.addini <pytest.Parser.addini>`, then the configuration
+        is treated as a string and a default empty string '' is returned.
+
         If the specified name hasn't been registered through a prior
         :func:`parser.addini <pytest.Parser.addini>` call (usually from a
         plugin), a ValueError is raised.
@@ -1370,9 +1619,11 @@ def getini(self, name: str):
 
     # Meant for easy monkeypatching by legacypath plugin.
     # Can be inlined back (with no cover removed) once legacypath is gone.
-    def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]):
-        msg = f"unknown configuration type: {type}"
-        raise ValueError(msg, value)  # pragma: no cover
+    def _getini_unknown_type(self, name: str, type: str, value: object):
+        msg = (
+            f"Option {name} has unknown configuration type {type} with value {value!r}"
+        )
+        raise ValueError(msg)  # pragma: no cover
 
     def _getini(self, name: str):
         try:
@@ -1384,11 +1635,7 @@ def _getini(self, name: str):
             try:
                 value = self.inicfg[name]
             except KeyError:
-                if default is not None:
-                    return default
-                if type is None:
-                    return ""
-                return []
+                return default
         else:
             value = override_value
         # Coerce the values based on types.
@@ -1407,9 +1654,11 @@ def _getini(self, name: str):
         #   in this case, we already have a list ready to use.
         #
         if type == "paths":
-            # TODO: This assert is probably not valid in all cases.
-            assert self.inipath is not None
-            dp = self.inipath.parent
+            dp = (
+                self.inipath.parent
+                if self.inipath is not None
+                else self.invocation_params.dir
+            )
             input_values = shlex.split(value) if isinstance(value, str) else value
             return [dp / x for x in input_values]
         elif type == "args":
@@ -1423,32 +1672,43 @@ def _getini(self, name: str):
             return _strtobool(str(value).strip())
         elif type == "string":
             return value
+        elif type == "int":
+            if not isinstance(value, str):
+                raise TypeError(
+                    f"Expected an int string for option {name} of type integer, but got: {value!r}"
+                ) from None
+            return int(value)
+        elif type == "float":
+            if not isinstance(value, str):
+                raise TypeError(
+                    f"Expected a float string for option {name} of type float, but got: {value!r}"
+                ) from None
+            return float(value)
         elif type is None:
             return value
         else:
             return self._getini_unknown_type(name, type, value)
 
     def _getconftest_pathlist(
-        self, name: str, path: Path, rootpath: Path
-    ) -> Optional[List[Path]]:
+        self, name: str, path: pathlib.Path
+    ) -> list[pathlib.Path] | None:
         try:
-            mod, relroots = self.pluginmanager._rget_with_confmod(
-                name, path, self.getoption("importmode"), rootpath
-            )
+            mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
         except KeyError:
             return None
-        modpath = Path(mod.__file__).parent
-        values: List[Path] = []
+        assert mod.__file__ is not None
+        modpath = pathlib.Path(mod.__file__).parent
+        values: list[pathlib.Path] = []
         for relroot in relroots:
             if isinstance(relroot, os.PathLike):
-                relroot = Path(relroot)
+                relroot = pathlib.Path(relroot)
             else:
                 relroot = relroot.replace("/", os.sep)
                 relroot = absolutepath(modpath / relroot)
             values.append(relroot)
         return values
 
-    def _get_override_ini_value(self, name: str) -> Optional[str]:
+    def _get_override_ini_value(self, name: str) -> str | None:
         value = None
         # override_ini is a list of "ini=value" options.
         # Always use the last item if multiple values are set for same ini-name,
@@ -1458,9 +1718,7 @@ def _get_override_ini_value(self, name: str) -> Optional[str]:
                 key, user_ini_value = ini_config.split("=", 1)
             except ValueError as e:
                 raise UsageError(
-                    "-o/--override-ini expects option=value style (got: {!r}).".format(
-                        ini_config
-                    )
+                    f"-o/--override-ini expects option=value style (got: {ini_config!r})."
                 ) from e
             else:
                 if key == name:
@@ -1470,11 +1728,12 @@ def _get_override_ini_value(self, name: str) -> Optional[str]:
     def getoption(self, name: str, default=notset, skip: bool = False):
         """Return command line option value.
 
-        :param name: Name of the option.  You may also specify
+        :param name: Name of the option. You may also specify
             the literal ``--OPT`` option instead of the "dest" option name.
-        :param default: Default value if no option of that name exists.
-        :param skip: If True, raise pytest.skip if option does not exists
-            or has a None value.
+        :param default: Fallback value if no option of that name is **declared** via :hook:`pytest_addoption`.
+            Note this parameter will be ignored when the option is **declared** even if the option's value is ``None``.
+        :param skip: If ``True``, raise :func:`pytest.skip` if option is undeclared or has a ``None`` value.
+            Note that even if ``True``, if a default was specified it will be returned instead of a skip.
         """
         name = self._opt2dest.get(name, name)
         try:
@@ -1499,6 +1758,80 @@ def getvalueorskip(self, name: str, path=None):
         """Deprecated, use getoption(skip=True) instead."""
         return self.getoption(name, skip=True)
 
+    #: Verbosity type for failed assertions (see :confval:`verbosity_assertions`).
+    VERBOSITY_ASSERTIONS: Final = "assertions"
+    #: Verbosity type for test case execution (see :confval:`verbosity_test_cases`).
+    VERBOSITY_TEST_CASES: Final = "test_cases"
+    _VERBOSITY_INI_DEFAULT: Final = "auto"
+
+    def get_verbosity(self, verbosity_type: str | None = None) -> int:
+        r"""Retrieve the verbosity level for a fine-grained verbosity type.
+
+        :param verbosity_type: Verbosity type to get level for. If a level is
+            configured for the given type, that value will be returned. If the
+            given type is not a known verbosity type, the global verbosity
+            level will be returned. If the given type is None (default), the
+            global verbosity level will be returned.
+
+        To configure a level for a fine-grained verbosity type, the
+        configuration file should have a setting for the configuration name
+        and a numeric value for the verbosity level. A special value of "auto"
+        can be used to explicitly use the global verbosity level.
+
+        Example:
+
+        .. code-block:: ini
+
+            # content of pytest.ini
+            [pytest]
+            verbosity_assertions = 2
+
+        .. code-block:: console
+
+            pytest -v
+
+        .. code-block:: python
+
+            print(config.get_verbosity())  # 1
+            print(config.get_verbosity(Config.VERBOSITY_ASSERTIONS))  # 2
+        """
+        global_level = self.getoption("verbose", default=0)
+        assert isinstance(global_level, int)
+        if verbosity_type is None:
+            return global_level
+
+        ini_name = Config._verbosity_ini_name(verbosity_type)
+        if ini_name not in self._parser._inidict:
+            return global_level
+
+        level = self.getini(ini_name)
+        if level == Config._VERBOSITY_INI_DEFAULT:
+            return global_level
+
+        return int(level)
+
+    @staticmethod
+    def _verbosity_ini_name(verbosity_type: str) -> str:
+        return f"verbosity_{verbosity_type}"
+
+    @staticmethod
+    def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None:
+        """Add a output verbosity configuration option for the given output type.
+
+        :param parser: Parser for command line arguments and ini-file values.
+        :param verbosity_type: Fine-grained verbosity category.
+        :param help: Description of the output this type controls.
+
+        The value should be retrieved via a call to
+        :py:func:`config.get_verbosity(type) <pytest.Config.get_verbosity>`.
+        """
+        parser.addini(
+            Config._verbosity_ini_name(verbosity_type),
+            help=help,
+            type="string",
+            default=Config._VERBOSITY_INI_DEFAULT,
+        )
+
     def _warn_about_missing_assertion(self, mode: str) -> None:
         if not _assertion_supported():
             if mode == "plain":
@@ -1538,7 +1871,7 @@ def _assertion_supported() -> bool:
 
 
 def create_terminal_writer(
-    config: Config, file: Optional[TextIO] = None
+    config: Config, file: TextIO | None = None
 ) -> TerminalWriter:
     """Create a TerminalWriter instance configured according to the options
     in the config object.
@@ -1582,7 +1915,7 @@ def _strtobool(val: str) -> bool:
 @lru_cache(maxsize=50)
 def parse_warning_filter(
     arg: str, *, escape: bool
-) -> Tuple[str, str, Type[Warning], str, int]:
+) -> tuple[warnings._ActionKind, str, type[Warning], str, int]:
     """Parse a warnings filter string.
 
     This is copied from warnings._setoption with the following changes:
@@ -1624,15 +1957,15 @@ def parse_warning_filter(
         parts.append("")
     action_, message, category_, module, lineno_ = (s.strip() for s in parts)
     try:
-        action: str = warnings._getaction(action_)  # type: ignore[attr-defined]
+        action: warnings._ActionKind = warnings._getaction(action_)  # type: ignore[attr-defined]
     except warnings._OptionError as e:
-        raise UsageError(error_template.format(error=str(e)))
+        raise UsageError(error_template.format(error=str(e))) from None
     try:
-        category: Type[Warning] = _resolve_warning_category(category_)
+        category: type[Warning] = _resolve_warning_category(category_)
     except Exception:
         exc_info = ExceptionInfo.from_current()
         exception_text = exc_info.getrepr(style="native")
-        raise UsageError(error_template.format(error=exception_text))
+        raise UsageError(error_template.format(error=exception_text)) from None
     if message and escape:
         message = re.escape(message)
     if module and escape:
@@ -1645,13 +1978,20 @@ def parse_warning_filter(
         except ValueError as e:
             raise UsageError(
                 error_template.format(error=f"invalid lineno {lineno_!r}: {e}")
-            )
+            ) from None
     else:
         lineno = 0
+    try:
+        re.compile(message)
+        re.compile(module)
+    except re.error as e:
+        raise UsageError(
+            error_template.format(error=f"Invalid regex {e.pattern!r}: {e}")
+        ) from None
     return action, message, category, module, lineno
 
 
-def _resolve_warning_category(category: str) -> Type[Warning]:
+def _resolve_warning_category(category: str) -> type[Warning]:
     """
     Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors)
     propagate so we can get access to their tracebacks (#9218).
@@ -1670,7 +2010,7 @@ def _resolve_warning_category(category: str) -> Type[Warning]:
     cat = getattr(m, klass)
     if not issubclass(cat, Warning):
         raise UsageError(f"{cat} is not a Warning subclass")
-    return cast(Type[Warning], cat)
+    return cast(type[Warning], cat)
 
 
 def apply_warning_filters(
diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py
index b0bb3f168ff..948dfe8a510 100644
--- a/src/_pytest/config/argparsing.py
+++ b/src/_pytest/config/argparsing.py
@@ -1,35 +1,33 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import argparse
+from collections.abc import Callable
+from collections.abc import Mapping
+from collections.abc import Sequence
 import os
-import sys
-import warnings
-from gettext import gettext
 from typing import Any
-from typing import Callable
 from typing import cast
-from typing import Dict
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import TYPE_CHECKING
-from typing import Union
+from typing import final
+from typing import Literal
+from typing import NoReturn
 
 import _pytest._io
-from _pytest.compat import final
 from _pytest.config.exceptions import UsageError
-from _pytest.deprecated import ARGUMENT_PERCENT_DEFAULT
-from _pytest.deprecated import ARGUMENT_TYPE_STR
-from _pytest.deprecated import ARGUMENT_TYPE_STR_CHOICE
 from _pytest.deprecated import check_ispytest
 
-if TYPE_CHECKING:
-    from typing import NoReturn
-    from typing_extensions import Literal
 
 FILE_OR_DIR = "file_or_dir"
 
 
+class NotSet:
+    def __repr__(self) -> str:
+        return "<notset>"
+
+
+NOT_SET = NotSet()
+
+
 @final
 class Parser:
     """Parser for command line arguments and ini-file values.
@@ -38,42 +36,43 @@ class Parser:
         there's an error processing the command line arguments.
     """
 
-    prog: Optional[str] = None
+    prog: str | None = None
 
     def __init__(
         self,
-        usage: Optional[str] = None,
-        processopt: Optional[Callable[["Argument"], None]] = None,
+        usage: str | None = None,
+        processopt: Callable[[Argument], None] | None = None,
         *,
         _ispytest: bool = False,
     ) -> None:
         check_ispytest(_ispytest)
-        self._anonymous = OptionGroup("custom options", parser=self, _ispytest=True)
-        self._groups: List[OptionGroup] = []
+        self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True)
+        self._groups: list[OptionGroup] = []
         self._processopt = processopt
         self._usage = usage
-        self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {}
-        self._ininames: List[str] = []
-        self.extra_info: Dict[str, Any] = {}
+        self._inidict: dict[str, tuple[str, str | None, Any]] = {}
+        self._ininames: list[str] = []
+        self.extra_info: dict[str, Any] = {}
 
-    def processoption(self, option: "Argument") -> None:
+    def processoption(self, option: Argument) -> None:
         if self._processopt:
             if option.dest:
                 self._processopt(option)
 
     def getgroup(
-        self, name: str, description: str = "", after: Optional[str] = None
-    ) -> "OptionGroup":
+        self, name: str, description: str = "", after: str | None = None
+    ) -> OptionGroup:
         """Get (or create) a named option Group.
 
-        :name: Name of the option group.
-        :description: Long description for --help output.
-        :after: Name of another group, used for ordering --help output.
+        :param name: Name of the option group.
+        :param description: Long description for --help output.
+        :param after: Name of another group, used for ordering --help output.
+        :returns: The option group.
 
         The returned group object has an ``addoption`` method with the same
         signature as :func:`parser.addoption <pytest.Parser.addoption>` but
         will be shown in the respective group in the output of
-        ``pytest. --help``.
+        ``pytest --help``.
         """
         for group in self._groups:
             if group.name == name:
@@ -89,10 +88,11 @@ def getgroup(
     def addoption(self, *opts: str, **attrs: Any) -> None:
         """Register a command line option.
 
-        :opts: Option names, can be short or long options.
-        :attrs: Same attributes which the ``add_argument()`` function of the
-           `argparse library <https://docs.python.org/library/argparse.html>`_
-           accepts.
+        :param opts:
+            Option names, can be short or long options.
+        :param attrs:
+            Same attributes as the argparse library's :meth:`add_argument()
+            <argparse.ArgumentParser.add_argument>` function accepts.
 
         After command line parsing, options are available on the pytest config
         object via ``config.option.NAME`` where ``NAME`` is usually set
@@ -103,8 +103,8 @@ def addoption(self, *opts: str, **attrs: Any) -> None:
 
     def parse(
         self,
-        args: Sequence[Union[str, "os.PathLike[str]"]],
-        namespace: Optional[argparse.Namespace] = None,
+        args: Sequence[str | os.PathLike[str]],
+        namespace: argparse.Namespace | None = None,
     ) -> argparse.Namespace:
         from _pytest._argcomplete import try_argcomplete
 
@@ -113,11 +113,11 @@ def parse(
         strargs = [os.fspath(x) for x in args]
         return self.optparser.parse_args(strargs, namespace=namespace)
 
-    def _getparser(self) -> "MyOptionParser":
+    def _getparser(self) -> MyOptionParser:
         from _pytest._argcomplete import filescompleter
 
         optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
-        groups = self._groups + [self._anonymous]
+        groups = [*self._groups, self._anonymous]
         for group in groups:
             if group.options:
                 desc = group.description or group.name
@@ -134,30 +134,38 @@ def _getparser(self) -> "MyOptionParser":
 
     def parse_setoption(
         self,
-        args: Sequence[Union[str, "os.PathLike[str]"]],
+        args: Sequence[str | os.PathLike[str]],
         option: argparse.Namespace,
-        namespace: Optional[argparse.Namespace] = None,
-    ) -> List[str]:
+        namespace: argparse.Namespace | None = None,
+    ) -> list[str]:
         parsedoption = self.parse(args, namespace=namespace)
         for name, value in parsedoption.__dict__.items():
             setattr(option, name, value)
-        return cast(List[str], getattr(parsedoption, FILE_OR_DIR))
+        return cast(list[str], getattr(parsedoption, FILE_OR_DIR))
 
     def parse_known_args(
         self,
-        args: Sequence[Union[str, "os.PathLike[str]"]],
-        namespace: Optional[argparse.Namespace] = None,
+        args: Sequence[str | os.PathLike[str]],
+        namespace: argparse.Namespace | None = None,
     ) -> argparse.Namespace:
-        """Parse and return a namespace object with known arguments at this point."""
+        """Parse the known arguments at this point.
+
+        :returns: An argparse namespace object.
+        """
         return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
 
     def parse_known_and_unknown_args(
         self,
-        args: Sequence[Union[str, "os.PathLike[str]"]],
-        namespace: Optional[argparse.Namespace] = None,
-    ) -> Tuple[argparse.Namespace, List[str]]:
-        """Parse and return a namespace object with known arguments, and
-        the remaining arguments unknown at this point."""
+        args: Sequence[str | os.PathLike[str]],
+        namespace: argparse.Namespace | None = None,
+    ) -> tuple[argparse.Namespace, list[str]]:
+        """Parse the known arguments at this point, and also return the
+        remaining unknown arguments.
+
+        :returns:
+            A tuple containing an argparse namespace object for the known
+            arguments, and a list of the unknown arguments.
+        """
         optparser = self._getparser()
         strargs = [os.fspath(x) for x in args]
         return optparser.parse_known_args(strargs, namespace=namespace)
@@ -166,16 +174,15 @@ def addini(
         self,
         name: str,
         help: str,
-        type: Optional[
-            "Literal['string', 'paths', 'pathlist', 'args', 'linelist', 'bool']"
-        ] = None,
-        default=None,
+        type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"]
+        | None = None,
+        default: Any = NOT_SET,
     ) -> None:
         """Register an ini-file option.
 
-        :name:
+        :param name:
             Name of the ini-variable.
-        :type:
+        :param type:
             Type of the variable. Can be:
 
                 * ``string``: a string
@@ -184,27 +191,77 @@ def addini(
                 * ``linelist``: a list of strings, separated by line breaks
                 * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell
                 * ``pathlist``: a list of ``py.path``, separated as in a shell
+                * ``int``: an integer
+                * ``float``: a floating-point number
+
+                .. versionadded:: 8.4
+
+                    The ``float`` and ``int`` types.
+
+            For ``paths`` and ``pathlist`` types, they are considered relative to the ini-file.
+            In case the execution is happening without an ini-file defined,
+            they will be considered relative to the current working directory (for example with ``--override-ini``).
 
             .. versionadded:: 7.0
                 The ``paths`` variable type.
 
+            .. versionadded:: 8.1
+                Use the current working directory to resolve ``paths`` and ``pathlist`` in the absence of an ini-file.
+
             Defaults to ``string`` if ``None`` or not passed.
-        :default:
+        :param default:
             Default value if no ini-file option exists but is queried.
 
         The value of ini-variables can be retrieved via a call to
         :py:func:`config.getini(name) <pytest.Config.getini>`.
         """
-        assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool")
+        assert type in (
+            None,
+            "string",
+            "paths",
+            "pathlist",
+            "args",
+            "linelist",
+            "bool",
+            "int",
+            "float",
+        )
+        if default is NOT_SET:
+            default = get_ini_default_for_type(type)
+
         self._inidict[name] = (help, type, default)
         self._ininames.append(name)
 
 
+def get_ini_default_for_type(
+    type: Literal[
+        "string", "paths", "pathlist", "args", "linelist", "bool", "int", "float"
+    ]
+    | None,
+) -> Any:
+    """
+    Used by addini to get the default value for a given ini-option type, when
+    default is not supplied.
+    """
+    if type is None:
+        return ""
+    elif type in ("paths", "pathlist", "args", "linelist"):
+        return []
+    elif type == "bool":
+        return False
+    elif type == "int":
+        return 0
+    elif type == "float":
+        return 0.0
+    else:
+        return ""
+
+
 class ArgumentError(Exception):
     """Raised if an Argument instance is created with invalid or
     inconsistent arguments."""
 
-    def __init__(self, msg: str, option: Union["Argument", str]) -> None:
+    def __init__(self, msg: str, option: Argument | str) -> None:
         self.msg = msg
         self.option_id = str(option)
 
@@ -224,46 +281,22 @@ class Argument:
     https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
     """
 
-    _typ_map = {"int": int, "string": str, "float": float, "complex": complex}
-
     def __init__(self, *names: str, **attrs: Any) -> None:
-        """Store parms in private vars for use in add_argument."""
+        """Store params in private vars for use in add_argument."""
         self._attrs = attrs
-        self._short_opts: List[str] = []
-        self._long_opts: List[str] = []
-        if "%default" in (attrs.get("help") or ""):
-            warnings.warn(ARGUMENT_PERCENT_DEFAULT, stacklevel=3)
+        self._short_opts: list[str] = []
+        self._long_opts: list[str] = []
         try:
-            typ = attrs["type"]
+            self.type = attrs["type"]
         except KeyError:
             pass
-        else:
-            # This might raise a keyerror as well, don't want to catch that.
-            if isinstance(typ, str):
-                if typ == "choice":
-                    warnings.warn(
-                        ARGUMENT_TYPE_STR_CHOICE.format(typ=typ, names=names),
-                        stacklevel=4,
-                    )
-                    # argparse expects a type here take it from
-                    # the type of the first element
-                    attrs["type"] = type(attrs["choices"][0])
-                else:
-                    warnings.warn(
-                        ARGUMENT_TYPE_STR.format(typ=typ, names=names), stacklevel=4
-                    )
-                    attrs["type"] = Argument._typ_map[typ]
-                # Used in test_parseopt -> test_parse_defaultgetter.
-                self.type = attrs["type"]
-            else:
-                self.type = typ
         try:
             # Attribute existence is tested in Config._processopt.
             self.default = attrs["default"]
         except KeyError:
             pass
         self._set_opt_strings(names)
-        dest: Optional[str] = attrs.get("dest")
+        dest: str | None = attrs.get("dest")
         if dest:
             self.dest = dest
         elif self._long_opts:
@@ -275,7 +308,7 @@ def __init__(self, *names: str, **attrs: Any) -> None:
                 self.dest = "???"  # Needed for the error repr.
                 raise ArgumentError("need a long or short option", self) from e
 
-    def names(self) -> List[str]:
+    def names(self) -> list[str]:
         return self._short_opts + self._long_opts
 
     def attrs(self) -> Mapping[str, Any]:
@@ -287,11 +320,6 @@ def attrs(self) -> Mapping[str, Any]:
                 self._attrs[attr] = getattr(self, attr)
             except AttributeError:
                 pass
-        if self._attrs.get("help"):
-            a = self._attrs["help"]
-            a = a.replace("%default", "%(default)s")
-            # a = a.replace('%prog', '%(prog)s')
-            self._attrs["help"] = a
         return self._attrs
 
     def _set_opt_strings(self, opts: Sequence[str]) -> None:
@@ -302,29 +330,29 @@ def _set_opt_strings(self, opts: Sequence[str]) -> None:
         for opt in opts:
             if len(opt) < 2:
                 raise ArgumentError(
-                    "invalid option string %r: "
-                    "must be at least two characters long" % opt,
+                    f"invalid option string {opt!r}: "
+                    "must be at least two characters long",
                     self,
                 )
             elif len(opt) == 2:
                 if not (opt[0] == "-" and opt[1] != "-"):
                     raise ArgumentError(
-                        "invalid short option string %r: "
-                        "must be of the form -x, (x any non-dash char)" % opt,
+                        f"invalid short option string {opt!r}: "
+                        "must be of the form -x, (x any non-dash char)",
                         self,
                     )
                 self._short_opts.append(opt)
             else:
                 if not (opt[0:2] == "--" and opt[2] != "-"):
                     raise ArgumentError(
-                        "invalid long option string %r: "
-                        "must start with --, followed by non-dash" % opt,
+                        f"invalid long option string {opt!r}: "
+                        "must start with --, followed by non-dash",
                         self,
                     )
                 self._long_opts.append(opt)
 
     def __repr__(self) -> str:
-        args: List[str] = []
+        args: list[str] = []
         if self._short_opts:
             args += ["_short_opts: " + repr(self._short_opts)]
         if self._long_opts:
@@ -344,37 +372,43 @@ def __init__(
         self,
         name: str,
         description: str = "",
-        parser: Optional[Parser] = None,
+        parser: Parser | None = None,
         *,
         _ispytest: bool = False,
     ) -> None:
         check_ispytest(_ispytest)
         self.name = name
         self.description = description
-        self.options: List[Argument] = []
+        self.options: list[Argument] = []
         self.parser = parser
 
-    def addoption(self, *optnames: str, **attrs: Any) -> None:
+    def addoption(self, *opts: str, **attrs: Any) -> None:
         """Add an option to this group.
 
         If a shortened version of a long option is specified, it will
         be suppressed in the help. ``addoption('--twowords', '--two-words')``
         results in help showing ``--two-words`` only, but ``--twowords`` gets
         accepted **and** the automatic destination is in ``args.twowords``.
+
+        :param opts:
+            Option names, can be short or long options.
+        :param attrs:
+            Same attributes as the argparse library's :meth:`add_argument()
+            <argparse.ArgumentParser.add_argument>` function accepts.
         """
-        conflict = set(optnames).intersection(
+        conflict = set(opts).intersection(
             name for opt in self.options for name in opt.names()
         )
         if conflict:
-            raise ValueError("option names %s already added" % conflict)
-        option = Argument(*optnames, **attrs)
+            raise ValueError(f"option names {conflict} already added")
+        option = Argument(*opts, **attrs)
         self._addoption_instance(option, shortupper=False)
 
-    def _addoption(self, *optnames: str, **attrs: Any) -> None:
-        option = Argument(*optnames, **attrs)
+    def _addoption(self, *opts: str, **attrs: Any) -> None:
+        option = Argument(*opts, **attrs)
         self._addoption_instance(option, shortupper=True)
 
-    def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None:
+    def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None:
         if not shortupper:
             for opt in option._short_opts:
                 if opt[0] == "-" and opt[1].islower():
@@ -388,8 +422,8 @@ class MyOptionParser(argparse.ArgumentParser):
     def __init__(
         self,
         parser: Parser,
-        extra_info: Optional[Dict[str, Any]] = None,
-        prog: Optional[str] = None,
+        extra_info: dict[str, Any] | None = None,
+        prog: str | None = None,
     ) -> None:
         self._parser = parser
         super().__init__(
@@ -398,77 +432,41 @@ def __init__(
             add_help=False,
             formatter_class=DropShorterLongHelpFormatter,
             allow_abbrev=False,
+            fromfile_prefix_chars="@",
         )
         # extra_info is a dict of (param -> value) to display if there's
         # an usage error to provide more contextual information to the user.
         self.extra_info = extra_info if extra_info else {}
 
-    def error(self, message: str) -> "NoReturn":
+    def error(self, message: str) -> NoReturn:
         """Transform argparse error message into UsageError."""
         msg = f"{self.prog}: error: {message}"
 
         if hasattr(self._parser, "_config_source_hint"):
-            # Type ignored because the attribute is set dynamically.
-            msg = f"{msg} ({self._parser._config_source_hint})"  # type: ignore
+            msg = f"{msg} ({self._parser._config_source_hint})"
 
         raise UsageError(self.format_usage() + msg)
 
     # Type ignored because typeshed has a very complex type in the superclass.
     def parse_args(  # type: ignore
         self,
-        args: Optional[Sequence[str]] = None,
-        namespace: Optional[argparse.Namespace] = None,
+        args: Sequence[str] | None = None,
+        namespace: argparse.Namespace | None = None,
     ) -> argparse.Namespace:
         """Allow splitting of positional arguments."""
         parsed, unrecognized = self.parse_known_args(args, namespace)
         if unrecognized:
             for arg in unrecognized:
                 if arg and arg[0] == "-":
-                    lines = ["unrecognized arguments: %s" % (" ".join(unrecognized))]
+                    lines = [
+                        "unrecognized arguments: {}".format(" ".join(unrecognized))
+                    ]
                     for k, v in sorted(self.extra_info.items()):
                         lines.append(f"  {k}: {v}")
                     self.error("\n".join(lines))
             getattr(parsed, FILE_OR_DIR).extend(unrecognized)
         return parsed
 
-    if sys.version_info[:2] < (3, 9):  # pragma: no cover
-        # Backport of https://github.com/python/cpython/pull/14316 so we can
-        # disable long --argument abbreviations without breaking short flags.
-        def _parse_optional(
-            self, arg_string: str
-        ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]:
-            if not arg_string:
-                return None
-            if not arg_string[0] in self.prefix_chars:
-                return None
-            if arg_string in self._option_string_actions:
-                action = self._option_string_actions[arg_string]
-                return action, arg_string, None
-            if len(arg_string) == 1:
-                return None
-            if "=" in arg_string:
-                option_string, explicit_arg = arg_string.split("=", 1)
-                if option_string in self._option_string_actions:
-                    action = self._option_string_actions[option_string]
-                    return action, option_string, explicit_arg
-            if self.allow_abbrev or not arg_string.startswith("--"):
-                option_tuples = self._get_option_tuples(arg_string)
-                if len(option_tuples) > 1:
-                    msg = gettext(
-                        "ambiguous option: %(option)s could match %(matches)s"
-                    )
-                    options = ", ".join(option for _, option, _ in option_tuples)
-                    self.error(msg % {"option": arg_string, "matches": options})
-                elif len(option_tuples) == 1:
-                    (option_tuple,) = option_tuples
-                    return option_tuple
-            if self._negative_number_matcher.match(arg_string):
-                if not self._has_negative_number_optionals:
-                    return None
-            if " " in arg_string:
-                return None
-            return None, arg_string, None
-
 
 class DropShorterLongHelpFormatter(argparse.HelpFormatter):
     """Shorten help for long options that differ only in extra hyphens.
@@ -488,7 +486,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str:
         orgstr = super()._format_action_invocation(action)
         if orgstr and orgstr[0] != "-":  # only optional arguments
             return orgstr
-        res: Optional[str] = getattr(action, "_formatted_action_invocation", None)
+        res: str | None = getattr(action, "_formatted_action_invocation", None)
         if res:
             return res
         options = orgstr.split(", ")
@@ -497,13 +495,13 @@ def _format_action_invocation(self, action: argparse.Action) -> str:
             action._formatted_action_invocation = orgstr  # type: ignore
             return orgstr
         return_list = []
-        short_long: Dict[str, str] = {}
+        short_long: dict[str, str] = {}
         for option in options:
             if len(option) == 2 or option[2] == " ":
                 continue
             if not option.startswith("--"):
                 raise ArgumentError(
-                    'long optional argument without "--": [%s]' % (option), option
+                    f'long optional argument without "--": [{option}]', option
                 )
             xxoption = option[2:]
             shortened = xxoption.replace("-", "")
diff --git a/src/_pytest/config/compat.py b/src/_pytest/config/compat.py
index 8f82dd9f9d7..21eab4c7e47 100644
--- a/src/_pytest/config/compat.py
+++ b/src/_pytest/config/compat.py
@@ -1,15 +1,20 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
 import functools
-import warnings
 from pathlib import Path
-from typing import Optional
+from typing import Any
+import warnings
+
+import pluggy
 
 from ..compat import LEGACY_PATH
 from ..compat import legacy_path
 from ..deprecated import HOOK_LEGACY_PATH_ARG
-from _pytest.nodes import _check_path
+
 
 # hookname: (Path, LEGACY_PATH)
-imply_paths_hooks = {
+imply_paths_hooks: Mapping[str, tuple[str, str]] = {
     "pytest_ignore_collect": ("collection_path", "path"),
     "pytest_collect_file": ("file_path", "path"),
     "pytest_pycollect_makemodule": ("module_path", "path"),
@@ -18,34 +23,41 @@
 }
 
 
+def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
+    if Path(fspath) != path:
+        raise ValueError(
+            f"Path({fspath!r}) != {path!r}\n"
+            "if both path and fspath are given they need to be equal"
+        )
+
+
 class PathAwareHookProxy:
     """
     this helper wraps around hook callers
     until pluggy supports fixingcalls, this one will do
 
-    it currently doesnt return full hook caller proxies for fixed hooks,
+    it currently doesn't return full hook caller proxies for fixed hooks,
     this may have to be changed later depending on bugs
     """
 
-    def __init__(self, hook_caller):
-        self.__hook_caller = hook_caller
+    def __init__(self, hook_relay: pluggy.HookRelay) -> None:
+        self._hook_relay = hook_relay
 
-    def __dir__(self):
-        return dir(self.__hook_caller)
+    def __dir__(self) -> list[str]:
+        return dir(self._hook_relay)
 
-    def __getattr__(self, key, _wraps=functools.wraps):
-        hook = getattr(self.__hook_caller, key)
+    def __getattr__(self, key: str) -> pluggy.HookCaller:
+        hook: pluggy.HookCaller = getattr(self._hook_relay, key)
         if key not in imply_paths_hooks:
             self.__dict__[key] = hook
             return hook
         else:
             path_var, fspath_var = imply_paths_hooks[key]
 
-            @_wraps(hook)
-            def fixed_hook(**kw):
-
-                path_value: Optional[Path] = kw.pop(path_var, None)
-                fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None)
+            @functools.wraps(hook)
+            def fixed_hook(**kw: Any) -> Any:
+                path_value: Path | None = kw.pop(path_var, None)
+                fspath_value: LEGACY_PATH | None = kw.pop(fspath_var, None)
                 if fspath_value is not None:
                     warnings.warn(
                         HOOK_LEGACY_PATH_ARG.format(
@@ -66,6 +78,8 @@ def fixed_hook(**kw):
                 kw[fspath_var] = fspath_value
                 return hook(**kw)
 
+            fixed_hook.name = hook.name  # type: ignore[attr-defined]
+            fixed_hook.spec = hook.spec  # type: ignore[attr-defined]
             fixed_hook.__name__ = key
             self.__dict__[key] = fixed_hook
-            return fixed_hook
+            return fixed_hook  # type: ignore[return-value]
diff --git a/src/_pytest/config/exceptions.py b/src/_pytest/config/exceptions.py
index 4f1320e758d..90108eca904 100644
--- a/src/_pytest/config/exceptions.py
+++ b/src/_pytest/config/exceptions.py
@@ -1,4 +1,6 @@
-from _pytest.compat import final
+from __future__ import annotations
+
+from typing import final
 
 
 @final
diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py
index 89ade5f23b9..15bfbb0613e 100644
--- a/src/_pytest/config/findpaths.py
+++ b/src/_pytest/config/findpaths.py
@@ -1,13 +1,11 @@
+from __future__ import annotations
+
+from collections.abc import Iterable
+from collections.abc import Sequence
 import os
 from pathlib import Path
-from typing import Dict
-from typing import Iterable
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
+import sys
 from typing import TYPE_CHECKING
-from typing import Union
 
 import iniconfig
 
@@ -15,9 +13,17 @@
 from _pytest.outcomes import fail
 from _pytest.pathlib import absolutepath
 from _pytest.pathlib import commonpath
+from _pytest.pathlib import safe_exists
+
 
 if TYPE_CHECKING:
-    from . import Config
+    from typing import Union
+
+    from typing_extensions import TypeAlias
+
+    # Even though TOML supports richer data types, all values are converted to str/list[str] during
+    # parsing to maintain compatibility with the rest of the configuration system.
+    ConfigDict: TypeAlias = dict[str, Union[str, list[str]]]
 
 
 def _parse_ini_config(path: Path) -> iniconfig.IniConfig:
@@ -34,12 +40,11 @@ def _parse_ini_config(path: Path) -> iniconfig.IniConfig:
 
 def load_config_dict_from_file(
     filepath: Path,
-) -> Optional[Dict[str, Union[str, List[str]]]]:
+) -> ConfigDict | None:
     """Load pytest configuration from the given file path, if supported.
 
     Return None if the file does not contain valid pytest configuration.
     """
-
     # Configuration from ini files are obtained from the [pytest] section, if present.
     if filepath.suffix == ".ini":
         iniconfig = _parse_ini_config(filepath)
@@ -64,20 +69,23 @@ def load_config_dict_from_file(
 
     # '.toml' files are considered if they contain a [tool.pytest.ini_options] table.
     elif filepath.suffix == ".toml":
-        import tomli
+        if sys.version_info >= (3, 11):
+            import tomllib
+        else:
+            import tomli as tomllib
 
         toml_text = filepath.read_text(encoding="utf-8")
         try:
-            config = tomli.loads(toml_text)
-        except tomli.TOMLDecodeError as exc:
-            raise UsageError(str(exc)) from exc
+            config = tomllib.loads(toml_text)
+        except tomllib.TOMLDecodeError as exc:
+            raise UsageError(f"{filepath}: {exc}") from exc
 
         result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
         if result is not None:
             # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc),
             # however we need to convert all scalar values to str for compatibility with the rest
             # of the configuration system, which expects strings only.
-            def make_scalar(v: object) -> Union[str, List[str]]:
+            def make_scalar(v: object) -> str | list[str]:
                 return v if isinstance(v, list) else str(v)
 
             return {k: make_scalar(v) for k, v in result.items()}
@@ -86,33 +94,43 @@ def make_scalar(v: object) -> Union[str, List[str]]:
 
 
 def locate_config(
+    invocation_dir: Path,
     args: Iterable[Path],
-) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]:
+) -> tuple[Path | None, Path | None, ConfigDict]:
     """Search in the list of arguments for a valid ini-file for pytest,
     and return a tuple of (rootdir, inifile, cfg-dict)."""
     config_names = [
         "pytest.ini",
+        ".pytest.ini",
         "pyproject.toml",
         "tox.ini",
         "setup.cfg",
     ]
     args = [x for x in args if not str(x).startswith("-")]
     if not args:
-        args = [Path.cwd()]
+        args = [invocation_dir]
+    found_pyproject_toml: Path | None = None
     for arg in args:
         argpath = absolutepath(arg)
         for base in (argpath, *argpath.parents):
             for config_name in config_names:
                 p = base / config_name
                 if p.is_file():
+                    if p.name == "pyproject.toml" and found_pyproject_toml is None:
+                        found_pyproject_toml = p
                     ini_config = load_config_dict_from_file(p)
                     if ini_config is not None:
                         return base, p, ini_config
+    if found_pyproject_toml is not None:
+        return found_pyproject_toml.parent, found_pyproject_toml, {}
     return None, None, {}
 
 
-def get_common_ancestor(paths: Iterable[Path]) -> Path:
-    common_ancestor: Optional[Path] = None
+def get_common_ancestor(
+    invocation_dir: Path,
+    paths: Iterable[Path],
+) -> Path:
+    common_ancestor: Path | None = None
     for path in paths:
         if not path.exists():
             continue
@@ -128,13 +146,13 @@ def get_common_ancestor(paths: Iterable[Path]) -> Path:
                 if shared is not None:
                     common_ancestor = shared
     if common_ancestor is None:
-        common_ancestor = Path.cwd()
+        common_ancestor = invocation_dir
     elif common_ancestor.is_file():
         common_ancestor = common_ancestor.parent
     return common_ancestor
 
 
-def get_dirs_from_args(args: Iterable[str]) -> List[Path]:
+def get_dirs_from_args(args: Iterable[str]) -> list[Path]:
     def is_option(x: str) -> bool:
         return x.startswith("-")
 
@@ -146,14 +164,6 @@ def get_dir_from_path(path: Path) -> Path:
             return path
         return path.parent
 
-    def safe_exists(path: Path) -> bool:
-        # This can throw on paths that contain characters unrepresentable at the OS level,
-        # or with invalid syntax on Windows (https://bugs.python.org/issue35306)
-        try:
-            return path.exists()
-        except OSError:
-            return False
-
     # These look like paths but may not exist
     possible_paths = (
         absolutepath(get_file_part_from_node_id(arg))
@@ -168,22 +178,35 @@ def safe_exists(path: Path) -> bool:
 
 
 def determine_setup(
-    inifile: Optional[str],
+    *,
+    inifile: str | None,
     args: Sequence[str],
-    rootdir_cmd_arg: Optional[str] = None,
-    config: Optional["Config"] = None,
-) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]:
+    rootdir_cmd_arg: str | None,
+    invocation_dir: Path,
+) -> tuple[Path, Path | None, ConfigDict]:
+    """Determine the rootdir, inifile and ini configuration values from the
+    command line arguments.
+
+    :param inifile:
+        The `--inifile` command line argument, if given.
+    :param args:
+        The free command line arguments.
+    :param rootdir_cmd_arg:
+        The `--rootdir` command line argument, if given.
+    :param invocation_dir:
+        The working directory when pytest was invoked.
+    """
     rootdir = None
     dirs = get_dirs_from_args(args)
     if inifile:
         inipath_ = absolutepath(inifile)
-        inipath: Optional[Path] = inipath_
+        inipath: Path | None = inipath_
         inicfg = load_config_dict_from_file(inipath_) or {}
         if rootdir_cmd_arg is None:
             rootdir = inipath_.parent
     else:
-        ancestor = get_common_ancestor(dirs)
-        rootdir, inipath, inicfg = locate_config([ancestor])
+        ancestor = get_common_ancestor(invocation_dir, dirs)
+        rootdir, inipath, inicfg = locate_config(invocation_dir, [ancestor])
         if rootdir is None and rootdir_cmd_arg is None:
             for possible_rootdir in (ancestor, *ancestor.parents):
                 if (possible_rootdir / "setup.py").is_file():
@@ -191,23 +214,26 @@ def determine_setup(
                     break
             else:
                 if dirs != [ancestor]:
-                    rootdir, inipath, inicfg = locate_config(dirs)
+                    rootdir, inipath, inicfg = locate_config(invocation_dir, dirs)
                 if rootdir is None:
-                    if config is not None:
-                        cwd = config.invocation_params.dir
-                    else:
-                        cwd = Path.cwd()
-                    rootdir = get_common_ancestor([cwd, ancestor])
-                    is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
-                    if is_fs_root:
+                    rootdir = get_common_ancestor(
+                        invocation_dir, [invocation_dir, ancestor]
+                    )
+                    if is_fs_root(rootdir):
                         rootdir = ancestor
     if rootdir_cmd_arg:
         rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg))
         if not rootdir.is_dir():
             raise UsageError(
-                "Directory '{}' not found. Check your '--rootdir' option.".format(
-                    rootdir
-                )
+                f"Directory '{rootdir}' not found. Check your '--rootdir' option."
             )
     assert rootdir is not None
     return rootdir, inipath, inicfg or {}
+
+
+def is_fs_root(p: Path) -> bool:
+    r"""
+    Return True if the given path is pointing to the root of the
+    file system ("/" on Unix and "C:\\" on Windows for example).
+    """
+    return os.path.splitdrive(str(p))[1] == os.sep
diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py
index 452fb18ac34..de1b2688f76 100644
--- a/src/_pytest/debugging.py
+++ b/src/_pytest/debugging.py
@@ -1,20 +1,21 @@
+# mypy: allow-untyped-defs
+# ruff: noqa: T100
 """Interactive debugging with PDB, the Python Debugger."""
+
+from __future__ import annotations
+
 import argparse
+from collections.abc import Callable
+from collections.abc import Generator
 import functools
 import sys
 import types
 from typing import Any
-from typing import Callable
-from typing import Generator
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import Type
-from typing import TYPE_CHECKING
-from typing import Union
+import unittest
 
 from _pytest import outcomes
 from _pytest._code import ExceptionInfo
+from _pytest.capture import CaptureManager
 from _pytest.config import Config
 from _pytest.config import ConftestImportFailure
 from _pytest.config import hookimpl
@@ -23,13 +24,10 @@
 from _pytest.config.exceptions import UsageError
 from _pytest.nodes import Node
 from _pytest.reports import BaseReport
-
-if TYPE_CHECKING:
-    from _pytest.capture import CaptureManager
-    from _pytest.runner import CallInfo
+from _pytest.runner import CallInfo
 
 
-def _validate_usepdb_cls(value: str) -> Tuple[str, str]:
+def _validate_usepdb_cls(value: str) -> tuple[str, str]:
     """Validate syntax of --pdbcls option."""
     try:
         modname, classname = value.split(":")
@@ -42,25 +40,25 @@ def _validate_usepdb_cls(value: str) -> Tuple[str, str]:
 
 def pytest_addoption(parser: Parser) -> None:
     group = parser.getgroup("general")
-    group._addoption(
+    group.addoption(
         "--pdb",
         dest="usepdb",
         action="store_true",
-        help="start the interactive Python debugger on errors or KeyboardInterrupt.",
+        help="Start the interactive Python debugger on errors or KeyboardInterrupt",
     )
-    group._addoption(
+    group.addoption(
         "--pdbcls",
         dest="usepdb_cls",
         metavar="modulename:classname",
         type=_validate_usepdb_cls,
-        help="specify a custom interactive Python debugger for use with --pdb."
+        help="Specify a custom interactive Python debugger for use with --pdb."
         "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb",
     )
-    group._addoption(
+    group.addoption(
         "--trace",
         dest="trace",
         action="store_true",
-        help="Immediately break when running each test.",
+        help="Immediately break when running each test",
     )
 
 
@@ -94,22 +92,22 @@ def fin() -> None:
 class pytestPDB:
     """Pseudo PDB that defers to the real pdb."""
 
-    _pluginmanager: Optional[PytestPluginManager] = None
-    _config: Optional[Config] = None
-    _saved: List[
-        Tuple[Callable[..., None], Optional[PytestPluginManager], Optional[Config]]
+    _pluginmanager: PytestPluginManager | None = None
+    _config: Config | None = None
+    _saved: list[
+        tuple[Callable[..., None], PytestPluginManager | None, Config | None]
     ] = []
     _recursive_debug = 0
-    _wrapped_pdb_cls: Optional[Tuple[Type[Any], Type[Any]]] = None
+    _wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None
 
     @classmethod
-    def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]:
+    def _is_capturing(cls, capman: CaptureManager | None) -> str | bool:
         if capman:
             return capman.is_capturing()
         return False
 
     @classmethod
-    def _import_pdb_cls(cls, capman: Optional["CaptureManager"]):
+    def _import_pdb_cls(cls, capman: CaptureManager | None):
         if not cls._config:
             import pdb
 
@@ -148,12 +146,10 @@ def _import_pdb_cls(cls, capman: Optional["CaptureManager"]):
         return wrapped_cls
 
     @classmethod
-    def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]):
+    def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None):
         import _pytest.config
 
-        # Type ignored because mypy doesn't support "dynamic"
-        # inheritance like this.
-        class PytestPdbWrapper(pdb_cls):  # type: ignore[valid-type,misc]
+        class PytestPdbWrapper(pdb_cls):
             _pytest_capman = capman
             _continued = False
 
@@ -163,6 +159,9 @@ def do_debug(self, arg):
                 cls._recursive_debug -= 1
                 return ret
 
+            if hasattr(pdb_cls, "do_debug"):
+                do_debug.__doc__ = pdb_cls.do_debug.__doc__
+
             def do_continue(self, arg):
                 ret = super().do_continue(arg)
                 if cls._recursive_debug == 0:
@@ -178,8 +177,7 @@ def do_continue(self, arg):
                         else:
                             tw.sep(
                                 ">",
-                                "PDB continue (IO-capturing resumed for %s)"
-                                % capturing,
+                                f"PDB continue (IO-capturing resumed for {capturing})",
                             )
                         assert capman is not None
                         capman.resume()
@@ -190,15 +188,17 @@ def do_continue(self, arg):
                 self._continued = True
                 return ret
 
+            if hasattr(pdb_cls, "do_continue"):
+                do_continue.__doc__ = pdb_cls.do_continue.__doc__
+
             do_c = do_cont = do_continue
 
             def do_quit(self, arg):
-                """Raise Exit outcome when quit command is used in pdb.
-
-                This is a bit of a hack - it would be better if BdbQuit
-                could be handled, but this would require to wrap the
-                whole pytest run, and adjust the report etc.
-                """
+                # Raise Exit outcome when quit command is used in pdb.
+                #
+                # This is a bit of a hack - it would be better if BdbQuit
+                # could be handled, but this would require to wrap the
+                # whole pytest run, and adjust the report etc.
                 ret = super().do_quit(arg)
 
                 if cls._recursive_debug == 0:
@@ -206,6 +206,9 @@ def do_quit(self, arg):
 
                 return ret
 
+            if hasattr(pdb_cls, "do_quit"):
+                do_quit.__doc__ = pdb_cls.do_quit.__doc__
+
             do_q = do_quit
             do_exit = do_quit
 
@@ -240,7 +243,7 @@ def _init_pdb(cls, method, *args, **kwargs):
         import _pytest.config
 
         if cls._pluginmanager is None:
-            capman: Optional[CaptureManager] = None
+            capman: CaptureManager | None = None
         else:
             capman = cls._pluginmanager.getplugin("capturemanager")
         if capman:
@@ -262,8 +265,7 @@ def _init_pdb(cls, method, *args, **kwargs):
                     elif capturing:
                         tw.sep(
                             ">",
-                            "PDB %s (IO-capturing turned off for %s)"
-                            % (method, capturing),
+                            f"PDB {method} (IO-capturing turned off for {capturing})",
                         )
                     else:
                         tw.sep(">", f"PDB {method}")
@@ -284,7 +286,7 @@ def set_trace(cls, *args, **kwargs) -> None:
 
 class PdbInvoke:
     def pytest_exception_interact(
-        self, node: Node, call: "CallInfo[Any]", report: BaseReport
+        self, node: Node, call: CallInfo[Any], report: BaseReport
     ) -> None:
         capman = node.config.pluginmanager.getplugin("capturemanager")
         if capman:
@@ -293,21 +295,23 @@ def pytest_exception_interact(
             sys.stdout.write(out)
             sys.stdout.write(err)
         assert call.excinfo is not None
-        _enter_pdb(node, call.excinfo, report)
+
+        if not isinstance(call.excinfo.value, unittest.SkipTest):
+            _enter_pdb(node, call.excinfo, report)
 
     def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None:
-        tb = _postmortem_traceback(excinfo)
-        post_mortem(tb)
+        exc_or_tb = _postmortem_exc_or_tb(excinfo)
+        post_mortem(exc_or_tb)
 
 
 class PdbTrace:
-    @hookimpl(hookwrapper=True)
-    def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, object, object]:
         wrap_pytest_function_for_tracing(pyfuncitem)
-        yield
+        return (yield)
 
 
-def wrap_pytest_function_for_tracing(pyfuncitem):
+def wrap_pytest_function_for_tracing(pyfuncitem) -> None:
     """Change the Python function object of the given Function item by a
     wrapper which actually enters pdb before calling the python function
     itself, effectively leaving the user in the pdb prompt in the first
@@ -319,14 +323,14 @@ def wrap_pytest_function_for_tracing(pyfuncitem):
     # python < 3.7.4) runcall's first param is `func`, which means we'd get
     # an exception if one of the kwargs to testfunction was called `func`.
     @functools.wraps(testfunction)
-    def wrapper(*args, **kwargs):
+    def wrapper(*args, **kwargs) -> None:
         func = functools.partial(testfunction, *args, **kwargs)
         _pdb.runcall(func)
 
     pyfuncitem.obj = wrapper
 
 
-def maybe_wrap_pytest_function_for_tracing(pyfuncitem):
+def maybe_wrap_pytest_function_for_tracing(pyfuncitem) -> None:
     """Wrap the given pytestfunct item for tracing support if --trace was given in
     the command line."""
     if pyfuncitem.config.getvalue("trace"):
@@ -336,7 +340,7 @@ def maybe_wrap_pytest_function_for_tracing(pyfuncitem):
 def _enter_pdb(
     node: Node, excinfo: ExceptionInfo[BaseException], rep: BaseReport
 ) -> BaseReport:
-    # XXX we re-use the TerminalReporter's terminalwriter
+    # XXX we reuse the TerminalReporter's terminalwriter
     # because this seems to avoid some encoding related troubles
     # for not completely clear reasons.
     tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
@@ -358,31 +362,46 @@ def _enter_pdb(
     tw.sep(">", "traceback")
     rep.toterminal(tw)
     tw.sep(">", "entering PDB")
-    tb = _postmortem_traceback(excinfo)
+    tb_or_exc = _postmortem_exc_or_tb(excinfo)
     rep._pdbshown = True  # type: ignore[attr-defined]
-    post_mortem(tb)
+    post_mortem(tb_or_exc)
     return rep
 
 
-def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType:
+def _postmortem_exc_or_tb(
+    excinfo: ExceptionInfo[BaseException],
+) -> types.TracebackType | BaseException:
     from doctest import UnexpectedException
 
+    get_exc = sys.version_info >= (3, 13)
     if isinstance(excinfo.value, UnexpectedException):
         # A doctest.UnexpectedException is not useful for post_mortem.
         # Use the underlying exception instead:
-        return excinfo.value.exc_info[2]
+        underlying_exc = excinfo.value
+        if get_exc:
+            return underlying_exc.exc_info[1]
+
+        return underlying_exc.exc_info[2]
     elif isinstance(excinfo.value, ConftestImportFailure):
         # A config.ConftestImportFailure is not useful for post_mortem.
         # Use the underlying exception instead:
-        return excinfo.value.excinfo[2]
+        cause = excinfo.value.cause
+        if get_exc:
+            return cause
+
+        assert cause.__traceback__ is not None
+        return cause.__traceback__
     else:
         assert excinfo._excinfo is not None
+        if get_exc:
+            return excinfo._excinfo[1]
+
         return excinfo._excinfo[2]
 
 
-def post_mortem(t: types.TracebackType) -> None:
+def post_mortem(tb_or_exc: types.TracebackType | BaseException) -> None:
     p = pytestPDB._init_pdb("post_mortem")
     p.reset()
-    p.interaction(None, t)
+    p.interaction(None, tb_or_exc)
     if p.quitting:
         outcomes.exit("Quitting debugger")
diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py
index 5f1edd60727..a605c24e58f 100644
--- a/src/_pytest/deprecated.py
+++ b/src/_pytest/deprecated.py
@@ -8,13 +8,16 @@
 :class:`PytestWarning`, or :class:`UnformattedWarning`
 in case of warnings which need to format their messages.
 """
+
+from __future__ import annotations
+
 from warnings import warn
 
 from _pytest.warning_types import PytestDeprecationWarning
-from _pytest.warning_types import PytestRemovedIn7Warning
-from _pytest.warning_types import PytestRemovedIn8Warning
+from _pytest.warning_types import PytestRemovedIn9Warning
 from _pytest.warning_types import UnformattedWarning
 
+
 # set of plugins which have been integrated into the core; we use this list to ignore
 # them during registration to avoid conflicts
 DEPRECATED_EXTERNAL_PLUGINS = {
@@ -24,18 +27,6 @@
 }
 
 
-FILLFUNCARGS = UnformattedWarning(
-    PytestRemovedIn7Warning,
-    "{name} is deprecated, use "
-    "function._request._fillfixtures() instead if you cannot avoid reaching into internals.",
-)
-
-PYTEST_COLLECT_MODULE = UnformattedWarning(
-    PytestRemovedIn7Warning,
-    "pytest.collect.{name} was moved to pytest.{name}\n"
-    "Please update to the new name.",
-)
-
 # This can be* removed pytest 8, but it's harmless and common, so no rush to remove.
 # * If you're in the future: "could have been".
 YIELD_FIXTURE = PytestDeprecationWarning(
@@ -43,90 +34,37 @@
     "Use @pytest.fixture instead; they are the same."
 )
 
-MINUS_K_DASH = PytestRemovedIn7Warning(
-    "The `-k '-expr'` syntax to -k is deprecated.\nUse `-k 'not expr'` instead."
-)
-
-MINUS_K_COLON = PytestRemovedIn7Warning(
-    "The `-k 'expr:'` syntax to -k is deprecated.\n"
-    "Please open an issue if you use this and want a replacement."
-)
-
-WARNING_CAPTURED_HOOK = PytestRemovedIn7Warning(
-    "The pytest_warning_captured is deprecated and will be removed in a future release.\n"
-    "Please use pytest_warning_recorded instead."
-)
-
-WARNING_CMDLINE_PREPARSE_HOOK = PytestRemovedIn8Warning(
-    "The pytest_cmdline_preparse hook is deprecated and will be removed in a future release. \n"
-    "Please use pytest_load_initial_conftests hook instead."
-)
-
-FSCOLLECTOR_GETHOOKPROXY_ISINITPATH = PytestRemovedIn8Warning(
-    "The gethookproxy() and isinitpath() methods of FSCollector and Package are deprecated; "
-    "use self.session.gethookproxy() and self.session.isinitpath() instead. "
-)
-
-STRICT_OPTION = PytestRemovedIn8Warning(
-    "The --strict option is deprecated, use --strict-markers instead."
-)
-
 # This deprecation is never really meant to be removed.
 PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")
 
-UNITTEST_SKIP_DURING_COLLECTION = PytestRemovedIn8Warning(
-    "Raising unittest.SkipTest to skip tests during collection is deprecated. "
-    "Use pytest.skip() instead."
-)
-
-ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning(
-    'pytest now uses argparse. "%default" should be changed to "%(default)s"',
-)
-
-ARGUMENT_TYPE_STR_CHOICE = UnformattedWarning(
-    PytestRemovedIn8Warning,
-    "`type` argument to addoption() is the string {typ!r}."
-    " For choices this is optional and can be omitted, "
-    " but when supplied should be a type (for example `str` or `int`)."
-    " (options: {names})",
-)
-
-ARGUMENT_TYPE_STR = UnformattedWarning(
-    PytestRemovedIn8Warning,
-    "`type` argument to addoption() is the string {typ!r}, "
-    " but when supplied should be a type (for example `str` or `int`)."
-    " (options: {names})",
-)
-
 
 HOOK_LEGACY_PATH_ARG = UnformattedWarning(
-    PytestRemovedIn8Warning,
+    PytestRemovedIn9Warning,
     "The ({pylib_path_arg}: py.path.local) argument is deprecated, please use ({pathlib_path_arg}: pathlib.Path)\n"
     "see https://docs.pytest.org/en/latest/deprecations.html"
     "#py-path-local-arguments-for-hooks-replaced-with-pathlib-path",
 )
 
 NODE_CTOR_FSPATH_ARG = UnformattedWarning(
-    PytestRemovedIn8Warning,
+    PytestRemovedIn9Warning,
     "The (fspath: py.path.local) argument to {node_type_name} is deprecated. "
     "Please use the (path: pathlib.Path) argument instead.\n"
     "See https://docs.pytest.org/en/latest/deprecations.html"
     "#fspath-argument-for-node-constructors-replaced-with-pathlib-path",
 )
 
-WARNS_NONE_ARG = PytestRemovedIn8Warning(
-    "Passing None to catch any warning has been deprecated, pass no arguments instead:\n"
-    " Replace pytest.warns(None) by simply pytest.warns()."
-)
-
-KEYWORD_MSG_ARG = UnformattedWarning(
-    PytestRemovedIn8Warning,
-    "pytest.{func}(msg=...) is now deprecated, use pytest.{func}(reason=...) instead",
+HOOK_LEGACY_MARKING = UnformattedWarning(
+    PytestDeprecationWarning,
+    "The hook{type} {fullname} uses old-style configuration options (marks or attributes).\n"
+    "Please use the pytest.hook{type}({hook_opts}) decorator instead\n"
+    " to configure the hooks.\n"
+    " See https://docs.pytest.org/en/latest/deprecations.html"
+    "#configuring-hook-specs-impls-using-markers",
 )
 
-INSTANCE_COLLECTOR = PytestRemovedIn8Warning(
-    "The pytest.Instance collector type is deprecated and is no longer used. "
-    "See https://docs.pytest.org/en/latest/deprecations.html#the-pytest-instance-collector",
+MARKED_FIXTURE = PytestRemovedIn9Warning(
+    "Marks applied to fixtures have no effect\n"
+    "See docs: https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function"
 )
 
 # You want to make some `__init__` or function "private".
diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py
index 0784f431b8e..0dbef6056d7 100644
--- a/src/_pytest/doctest.py
+++ b/src/_pytest/doctest.py
@@ -1,29 +1,27 @@
+# mypy: allow-untyped-defs
 """Discover and run doctests in modules and test files."""
+
+from __future__ import annotations
+
 import bdb
+from collections.abc import Callable
+from collections.abc import Generator
+from collections.abc import Iterable
+from collections.abc import Sequence
+from contextlib import contextmanager
+import functools
 import inspect
 import os
+from pathlib import Path
 import platform
+import re
 import sys
 import traceback
 import types
-import warnings
-from contextlib import contextmanager
-from pathlib import Path
 from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import Generator
-from typing import Iterable
-from typing import List
-from typing import Optional
-from typing import Pattern
-from typing import Sequence
-from typing import Tuple
-from typing import Type
 from typing import TYPE_CHECKING
-from typing import Union
+import warnings
 
-import pytest
 from _pytest import outcomes
 from _pytest._code.code import ExceptionInfo
 from _pytest._code.code import ReprFileLocation
@@ -32,17 +30,23 @@
 from _pytest.compat import safe_getattr
 from _pytest.config import Config
 from _pytest.config.argparsing import Parser
-from _pytest.fixtures import FixtureRequest
+from _pytest.fixtures import fixture
+from _pytest.fixtures import TopRequest
 from _pytest.nodes import Collector
+from _pytest.nodes import Item
 from _pytest.outcomes import OutcomeException
+from _pytest.outcomes import skip
 from _pytest.pathlib import fnmatch_ex
-from _pytest.pathlib import import_path
+from _pytest.python import Module
 from _pytest.python_api import approx
 from _pytest.warning_types import PytestWarning
 
+
 if TYPE_CHECKING:
     import doctest
 
+    from typing_extensions import Self
+
 DOCTEST_REPORT_CHOICE_NONE = "none"
 DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
 DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
@@ -60,32 +64,32 @@
 # Lazy definition of runner class
 RUNNER_CLASS = None
 # Lazy definition of output checker class
-CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None
+CHECKER_CLASS: type[doctest.OutputChecker] | None = None
 
 
 def pytest_addoption(parser: Parser) -> None:
     parser.addini(
         "doctest_optionflags",
-        "option flags for doctests",
+        "Option flags for doctests",
         type="args",
         default=["ELLIPSIS"],
     )
     parser.addini(
-        "doctest_encoding", "encoding used for doctest files", default="utf-8"
+        "doctest_encoding", "Encoding used for doctest files", default="utf-8"
     )
     group = parser.getgroup("collect")
     group.addoption(
         "--doctest-modules",
         action="store_true",
         default=False,
-        help="run doctests in all .py modules",
+        help="Run doctests in all .py modules",
         dest="doctestmodules",
     )
     group.addoption(
         "--doctest-report",
         type=str.lower,
         default="udiff",
-        help="choose another output format for diffs on doctest failure",
+        help="Choose another output format for diffs on doctest failure",
         choices=DOCTEST_REPORT_CHOICES,
         dest="doctestreport",
     )
@@ -94,21 +98,21 @@ def pytest_addoption(parser: Parser) -> None:
         action="append",
         default=[],
         metavar="pat",
-        help="doctests file matching pattern, default: test*.txt",
+        help="Doctests file matching pattern, default: test*.txt",
         dest="doctestglob",
     )
     group.addoption(
         "--doctest-ignore-import-errors",
         action="store_true",
         default=False,
-        help="ignore doctest ImportErrors",
+        help="Ignore doctest collection errors",
         dest="doctest_ignore_import_errors",
     )
     group.addoption(
         "--doctest-continue-on-failure",
         action="store_true",
         default=False,
-        help="for a given doctest, continue to run after the first failure",
+        help="For a given doctest, continue to run after the first failure",
         dest="doctest_continue_on_failure",
     )
 
@@ -122,17 +126,15 @@ def pytest_unconfigure() -> None:
 def pytest_collect_file(
     file_path: Path,
     parent: Collector,
-) -> Optional[Union["DoctestModule", "DoctestTextfile"]]:
+) -> DoctestModule | DoctestTextfile | None:
     config = parent.config
     if file_path.suffix == ".py":
         if config.option.doctestmodules and not any(
             (_is_setup_py(file_path), _is_main_py(file_path))
         ):
-            mod: DoctestModule = DoctestModule.from_parent(parent, path=file_path)
-            return mod
+            return DoctestModule.from_parent(parent, path=file_path)
     elif _is_doctest(config, file_path, parent):
-        txt: DoctestTextfile = DoctestTextfile.from_parent(parent, path=file_path)
-        return txt
+        return DoctestTextfile.from_parent(parent, path=file_path)
     return None
 
 
@@ -156,7 +158,7 @@ def _is_main_py(path: Path) -> bool:
 
 class ReprFailDoctest(TerminalRepr):
     def __init__(
-        self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]]
+        self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]]
     ) -> None:
         self.reprlocation_lines = reprlocation_lines
 
@@ -168,12 +170,12 @@ def toterminal(self, tw: TerminalWriter) -> None:
 
 
 class MultipleDoctestFailures(Exception):
-    def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None:
+    def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None:
         super().__init__()
         self.failures = failures
 
 
-def _init_runner_class() -> Type["doctest.DocTestRunner"]:
+def _init_runner_class() -> type[doctest.DocTestRunner]:
     import doctest
 
     class PytestDoctestRunner(doctest.DebugRunner):
@@ -185,8 +187,8 @@ class PytestDoctestRunner(doctest.DebugRunner):
 
         def __init__(
             self,
-            checker: Optional["doctest.OutputChecker"] = None,
-            verbose: Optional[bool] = None,
+            checker: doctest.OutputChecker | None = None,
+            verbose: bool | None = None,
             optionflags: int = 0,
             continue_on_failure: bool = True,
         ) -> None:
@@ -196,8 +198,8 @@ def __init__(
         def report_failure(
             self,
             out,
-            test: "doctest.DocTest",
-            example: "doctest.Example",
+            test: doctest.DocTest,
+            example: doctest.Example,
             got: str,
         ) -> None:
             failure = doctest.DocTestFailure(test, example, got)
@@ -209,9 +211,9 @@ def report_failure(
         def report_unexpected_exception(
             self,
             out,
-            test: "doctest.DocTest",
-            example: "doctest.Example",
-            exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType],
+            test: doctest.DocTest,
+            example: doctest.Example,
+            exc_info: tuple[type[BaseException], BaseException, types.TracebackType],
         ) -> None:
             if isinstance(exc_info[1], OutcomeException):
                 raise exc_info[1]
@@ -227,11 +229,11 @@ def report_unexpected_exception(
 
 
 def _get_runner(
-    checker: Optional["doctest.OutputChecker"] = None,
-    verbose: Optional[bool] = None,
+    checker: doctest.OutputChecker | None = None,
+    verbose: bool | None = None,
     optionflags: int = 0,
     continue_on_failure: bool = True,
-) -> "doctest.DocTestRunner":
+) -> doctest.DocTestRunner:
     # We need this in order to do a lazy import on doctest
     global RUNNER_CLASS
     if RUNNER_CLASS is None:
@@ -246,49 +248,54 @@ def _get_runner(
     )
 
 
-class DoctestItem(pytest.Item):
+class DoctestItem(Item):
     def __init__(
         self,
         name: str,
-        parent: "Union[DoctestTextfile, DoctestModule]",
-        runner: Optional["doctest.DocTestRunner"] = None,
-        dtest: Optional["doctest.DocTest"] = None,
+        parent: DoctestTextfile | DoctestModule,
+        runner: doctest.DocTestRunner,
+        dtest: doctest.DocTest,
     ) -> None:
         super().__init__(name, parent)
         self.runner = runner
         self.dtest = dtest
+
+        # Stuff needed for fixture support.
         self.obj = None
-        self.fixture_request: Optional[FixtureRequest] = None
+        fm = self.session._fixturemanager
+        fixtureinfo = fm.getfixtureinfo(node=self, func=None, cls=None)
+        self._fixtureinfo = fixtureinfo
+        self.fixturenames = fixtureinfo.names_closure
+        self._initrequest()
 
     @classmethod
-    def from_parent(  # type: ignore
+    def from_parent(  # type: ignore[override]
         cls,
-        parent: "Union[DoctestTextfile, DoctestModule]",
+        parent: DoctestTextfile | DoctestModule,
         *,
         name: str,
-        runner: "doctest.DocTestRunner",
-        dtest: "doctest.DocTest",
-    ):
+        runner: doctest.DocTestRunner,
+        dtest: doctest.DocTest,
+    ) -> Self:
         # incompatible signature due to imposed limits on subclass
         """The public named constructor."""
         return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)
 
+    def _initrequest(self) -> None:
+        self.funcargs: dict[str, object] = {}
+        self._request = TopRequest(self, _ispytest=True)  # type: ignore[arg-type]
+
     def setup(self) -> None:
-        if self.dtest is not None:
-            self.fixture_request = _setup_fixtures(self)
-            globs = dict(getfixture=self.fixture_request.getfixturevalue)
-            for name, value in self.fixture_request.getfixturevalue(
-                "doctest_namespace"
-            ).items():
-                globs[name] = value
-            self.dtest.globs.update(globs)
+        self._request._fillfixtures()
+        globs = dict(getfixture=self._request.getfixturevalue)
+        for name, value in self._request.getfixturevalue("doctest_namespace").items():
+            globs[name] = value
+        self.dtest.globs.update(globs)
 
     def runtest(self) -> None:
-        assert self.dtest is not None
-        assert self.runner is not None
         _check_all_skipped(self.dtest)
         self._disable_output_capturing_for_darwin()
-        failures: List["doctest.DocTestFailure"] = []
+        failures: list[doctest.DocTestFailure] = []
         # Type ignored because we change the type of `out` from what
         # doctest expects.
         self.runner.run(self.dtest, out=failures)  # type: ignore[arg-type]
@@ -310,12 +317,12 @@ def _disable_output_capturing_for_darwin(self) -> None:
     def repr_failure(  # type: ignore[override]
         self,
         excinfo: ExceptionInfo[BaseException],
-    ) -> Union[str, TerminalRepr]:
+    ) -> str | TerminalRepr:
         import doctest
 
-        failures: Optional[
-            Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]]
-        ] = None
+        failures: (
+            Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None
+        ) = None
         if isinstance(
             excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException)
         ):
@@ -346,7 +353,7 @@ def repr_failure(  # type: ignore[override]
                 # add line numbers to the left of the error message
                 assert test.lineno is not None
                 lines = [
-                    "%03d %s" % (i + test.lineno + 1, x) for (i, x) in enumerate(lines)
+                    f"{i + test.lineno + 1:03d} {x}" for (i, x) in enumerate(lines)
                 ]
                 # trim docstring error lines to 10
                 lines = lines[max(example.lineno - 9, 0) : example.lineno + 1]
@@ -364,19 +371,18 @@ def repr_failure(  # type: ignore[override]
                 ).split("\n")
             else:
                 inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info)
-                lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)]
+                lines += [f"UNEXPECTED EXCEPTION: {inner_excinfo.value!r}"]
                 lines += [
                     x.strip("\n") for x in traceback.format_exception(*failure.exc_info)
                 ]
             reprlocation_lines.append((reprlocation, lines))
         return ReprFailDoctest(reprlocation_lines)
 
-    def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
-        assert self.dtest is not None
-        return self.path, self.dtest.lineno, "[doctest] %s" % self.name
+    def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
+        return self.path, self.dtest.lineno, f"[doctest] {self.name}"
 
 
-def _get_flag_lookup() -> Dict[str, int]:
+def _get_flag_lookup() -> dict[str, int]:
     import doctest
 
     return dict(
@@ -392,8 +398,8 @@ def _get_flag_lookup() -> Dict[str, int]:
     )
 
 
-def get_optionflags(parent):
-    optionflags_str = parent.config.getini("doctest_optionflags")
+def get_optionflags(config: Config) -> int:
+    optionflags_str = config.getini("doctest_optionflags")
     flag_lookup_table = _get_flag_lookup()
     flag_acc = 0
     for flag in optionflags_str:
@@ -401,8 +407,8 @@ def get_optionflags(parent):
     return flag_acc
 
 
-def _get_continue_on_failure(config):
-    continue_on_failure = config.getvalue("doctest_continue_on_failure")
+def _get_continue_on_failure(config: Config) -> bool:
+    continue_on_failure: bool = config.getvalue("doctest_continue_on_failure")
     if continue_on_failure:
         # We need to turn off this if we use pdb since we should stop at
         # the first failure.
@@ -411,7 +417,7 @@ def _get_continue_on_failure(config):
     return continue_on_failure
 
 
-class DoctestTextfile(pytest.Module):
+class DoctestTextfile(Module):
     obj = None
 
     def collect(self) -> Iterable[DoctestItem]:
@@ -425,7 +431,7 @@ def collect(self) -> Iterable[DoctestItem]:
         name = self.path.name
         globs = {"__name__": "__main__"}
 
-        optionflags = get_optionflags(self)
+        optionflags = get_optionflags(self.config)
 
         runner = _get_runner(
             verbose=False,
@@ -442,14 +448,14 @@ def collect(self) -> Iterable[DoctestItem]:
             )
 
 
-def _check_all_skipped(test: "doctest.DocTest") -> None:
+def _check_all_skipped(test: doctest.DocTest) -> None:
     """Raise pytest.skip() if all examples in the given DocTest have the SKIP
     option set."""
     import doctest
 
     all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
     if all_skipped:
-        pytest.skip("all tests skipped by +SKIP option")
+        skip("all tests skipped by +SKIP option")
 
 
 def _is_mocked(obj: object) -> bool:
@@ -462,13 +468,13 @@ def _is_mocked(obj: object) -> bool:
 
 
 @contextmanager
-def _patch_unwrap_mock_aware() -> Generator[None, None, None]:
+def _patch_unwrap_mock_aware() -> Generator[None]:
     """Context manager which replaces ``inspect.unwrap`` with a version
     that's aware of mock objects and doesn't recurse into them."""
     real_unwrap = inspect.unwrap
 
     def _mock_aware_unwrap(
-        func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None
+        func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None
     ) -> Any:
         try:
             if stop is None or stop is _is_mocked:
@@ -477,9 +483,9 @@ def _mock_aware_unwrap(
             return real_unwrap(func, stop=lambda obj: _is_mocked(obj) or _stop(func))
         except Exception as e:
             warnings.warn(
-                "Got %r when unwrapping %r.  This is usually caused "
+                f"Got {e!r} when unwrapping {func!r}.  This is usually caused "
                 "by a violation of Python's object protocol; see e.g. "
-                "https://github.com/pytest-dev/pytest/issues/5080" % (e, func),
+                "https://github.com/pytest-dev/pytest/issues/5080",
                 PytestWarning,
             )
             raise
@@ -491,66 +497,86 @@ def _mock_aware_unwrap(
         inspect.unwrap = real_unwrap
 
 
-class DoctestModule(pytest.Module):
+class DoctestModule(Module):
     def collect(self) -> Iterable[DoctestItem]:
         import doctest
 
         class MockAwareDocTestFinder(doctest.DocTestFinder):
-            """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug.
-
-            https://github.com/pytest-dev/pytest/issues/3456
-            https://bugs.python.org/issue25532
-            """
-
-            def _find_lineno(self, obj, source_lines):
-                """Doctest code does not take into account `@property`, this
-                is a hackish way to fix it. https://bugs.python.org/issue17446
-
-                Wrapped Doctests will need to be unwrapped so the correct
-                line number is returned. This will be reported upstream. #8796
-                """
-                if isinstance(obj, property):
-                    obj = getattr(obj, "fget", obj)
-
-                if hasattr(obj, "__wrapped__"):
-                    # Get the main obj in case of it being wrapped
-                    obj = inspect.unwrap(obj)
-
-                # Type ignored because this is a private function.
-                return super()._find_lineno(  # type:ignore[misc]
-                    obj,
-                    source_lines,
-                )
+            py_ver_info_minor = sys.version_info[:2]
+            is_find_lineno_broken = (
+                py_ver_info_minor < (3, 11)
+                or (py_ver_info_minor == (3, 11) and sys.version_info.micro < 9)
+                or (py_ver_info_minor == (3, 12) and sys.version_info.micro < 3)
+            )
+            if is_find_lineno_broken:
+
+                def _find_lineno(self, obj, source_lines):
+                    """On older Pythons, doctest code does not take into account
+                    `@property`. https://github.com/python/cpython/issues/61648
 
-            def _find(
-                self, tests, obj, name, module, source_lines, globs, seen
-            ) -> None:
-                if _is_mocked(obj):
-                    return
-                with _patch_unwrap_mock_aware():
+                    Moreover, wrapped Doctests need to be unwrapped so the correct
+                    line number is returned. #8796
+                    """
+                    if isinstance(obj, property):
+                        obj = getattr(obj, "fget", obj)
+
+                    if hasattr(obj, "__wrapped__"):
+                        # Get the main obj in case of it being wrapped
+                        obj = inspect.unwrap(obj)
 
                     # Type ignored because this is a private function.
-                    super()._find(  # type:ignore[misc]
-                        tests, obj, name, module, source_lines, globs, seen
+                    return super()._find_lineno(  # type:ignore[misc]
+                        obj,
+                        source_lines,
                     )
 
-        if self.path.name == "conftest.py":
-            module = self.config.pluginmanager._importconftest(
-                self.path,
-                self.config.getoption("importmode"),
-                rootpath=self.config.rootpath,
-            )
-        else:
-            try:
-                module = import_path(self.path, root=self.config.rootpath)
-            except ImportError:
-                if self.config.getvalue("doctest_ignore_import_errors"):
-                    pytest.skip("unable to import module %r" % self.path)
-                else:
-                    raise
+            if sys.version_info < (3, 10):
+
+                def _find(
+                    self, tests, obj, name, module, source_lines, globs, seen
+                ) -> None:
+                    """Override _find to work around issue in stdlib.
+
+                    https://github.com/pytest-dev/pytest/issues/3456
+                    https://github.com/python/cpython/issues/69718
+                    """
+                    if _is_mocked(obj):
+                        return  # pragma: no cover
+                    with _patch_unwrap_mock_aware():
+                        # Type ignored because this is a private function.
+                        super()._find(  # type:ignore[misc]
+                            tests, obj, name, module, source_lines, globs, seen
+                        )
+
+            if sys.version_info < (3, 13):
+
+                def _from_module(self, module, object):
+                    """`cached_property` objects are never considered a part
+                    of the 'current module'. As such they are skipped by doctest.
+                    Here we override `_from_module` to check the underlying
+                    function instead. https://github.com/python/cpython/issues/107995
+                    """
+                    if isinstance(object, functools.cached_property):
+                        object = object.func
+
+                    # Type ignored because this is a private function.
+                    return super()._from_module(module, object)  # type: ignore[misc]
+
+        try:
+            module = self.obj
+        except Collector.CollectError:
+            if self.config.getvalue("doctest_ignore_import_errors"):
+                skip(f"unable to import module {self.path!r}")
+            else:
+                raise
+
+        # While doctests currently don't support fixtures directly, we still
+        # need to pick up autouse fixtures.
+        self.session._fixturemanager.parsefactories(self)
+
         # Uses internal doctest module parsing mechanism.
         finder = MockAwareDocTestFinder()
-        optionflags = get_optionflags(self)
+        optionflags = get_optionflags(self.config)
         runner = _get_runner(
             verbose=False,
             optionflags=optionflags,
@@ -565,25 +591,8 @@ def _find(
                 )
 
 
-def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest:
-    """Used by DoctestTextfile and DoctestItem to setup fixture information."""
-
-    def func() -> None:
-        pass
-
-    doctest_item.funcargs = {}  # type: ignore[attr-defined]
-    fm = doctest_item.session._fixturemanager
-    doctest_item._fixtureinfo = fm.getfixtureinfo(  # type: ignore[attr-defined]
-        node=doctest_item, func=func, cls=None, funcargs=False
-    )
-    fixture_request = FixtureRequest(doctest_item, _ispytest=True)
-    fixture_request._fillfixtures()
-    return fixture_request
-
-
-def _init_checker_class() -> Type["doctest.OutputChecker"]:
+def _init_checker_class() -> type[doctest.OutputChecker]:
     import doctest
-    import re
 
     class LiteralsOutputChecker(doctest.OutputChecker):
         # Based on doctest_nose_plugin.py from the nltk project
@@ -626,7 +635,7 @@ def check_output(self, want: str, got: str, optionflags: int) -> bool:
             if not allow_unicode and not allow_bytes and not allow_number:
                 return False
 
-            def remove_prefixes(regex: Pattern[str], txt: str) -> str:
+            def remove_prefixes(regex: re.Pattern[str], txt: str) -> str:
                 return re.sub(regex, r"\1\2", txt)
 
             if allow_unicode:
@@ -649,14 +658,14 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str:
                 return got
             offset = 0
             for w, g in zip(wants, gots):
-                fraction: Optional[str] = w.group("fraction")
-                exponent: Optional[str] = w.group("exponent1")
+                fraction: str | None = w.group("fraction")
+                exponent: str | None = w.group("exponent1")
                 if exponent is None:
                     exponent = w.group("exponent2")
                 precision = 0 if fraction is None else len(fraction)
                 if exponent is not None:
                     precision -= int(exponent)
-                if float(w.group()) == approx(float(g.group()), abs=10 ** -precision):
+                if float(w.group()) == approx(float(g.group()), abs=10**-precision):
                     # They're close enough. Replace the text we actually
                     # got with the text we want, so that it will match when we
                     # check the string literally.
@@ -669,7 +678,7 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str:
     return LiteralsOutputChecker
 
 
-def _get_checker() -> "doctest.OutputChecker":
+def _get_checker() -> doctest.OutputChecker:
     """Return a doctest.OutputChecker subclass that supports some
     additional options:
 
@@ -727,8 +736,19 @@ def _get_report_choice(key: str) -> int:
     }[key]
 
 
-@pytest.fixture(scope="session")
-def doctest_namespace() -> Dict[str, Any]:
+@fixture(scope="session")
+def doctest_namespace() -> dict[str, Any]:
     """Fixture that returns a :py:class:`dict` that will be injected into the
-    namespace of doctests."""
+    namespace of doctests.
+
+    Usually this fixture is used in conjunction with another ``autouse`` fixture:
+
+    .. code-block:: python
+
+        @pytest.fixture(autouse=True)
+        def add_np(doctest_namespace):
+            doctest_namespace["np"] = numpy
+
+    For more details: :ref:`doctest_namespace`.
+    """
     return dict()
diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py
index aaee307ff2c..79efc1d1704 100644
--- a/src/_pytest/faulthandler.py
+++ b/src/_pytest/faulthandler.py
@@ -1,24 +1,24 @@
-import io
+from __future__ import annotations
+
+from collections.abc import Generator
 import os
 import sys
-from typing import Generator
-from typing import TextIO
 
-import pytest
 from _pytest.config import Config
 from _pytest.config.argparsing import Parser
 from _pytest.nodes import Item
 from _pytest.stash import StashKey
+import pytest
 
 
-fault_handler_stderr_key = StashKey[TextIO]()
-fault_handler_originally_enabled_key = StashKey[bool]()
+fault_handler_original_stderr_fd_key = StashKey[int]()
+fault_handler_stderr_fd_key = StashKey[int]()
 
 
 def pytest_addoption(parser: Parser) -> None:
     help = (
         "Dump the traceback of all threads if a test takes "
-        "more than TIMEOUT seconds to finish."
+        "more than TIMEOUT seconds to finish"
     )
     parser.addini("faulthandler_timeout", help, default=0.0)
 
@@ -26,10 +26,16 @@ def pytest_addoption(parser: Parser) -> None:
 def pytest_configure(config: Config) -> None:
     import faulthandler
 
-    stderr_fd_copy = os.dup(get_stderr_fileno())
-    config.stash[fault_handler_stderr_key] = open(stderr_fd_copy, "w")
-    config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled()
-    faulthandler.enable(file=config.stash[fault_handler_stderr_key])
+    # at teardown we want to restore the original faulthandler fileno
+    # but faulthandler has no api to return the original fileno
+    # so here we stash the stderr fileno to be used at teardown
+    # sys.stderr and sys.__stderr__ may be closed or patched during the session
+    # so we can't rely on their values being good at that point (#11572).
+    stderr_fileno = get_stderr_fileno()
+    if faulthandler.is_enabled():
+        config.stash[fault_handler_original_stderr_fd_key] = stderr_fileno
+    config.stash[fault_handler_stderr_fd_key] = os.dup(stderr_fileno)
+    faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key])
 
 
 def pytest_unconfigure(config: Config) -> None:
@@ -37,12 +43,13 @@ def pytest_unconfigure(config: Config) -> None:
 
     faulthandler.disable()
     # Close the dup file installed during pytest_configure.
-    if fault_handler_stderr_key in config.stash:
-        config.stash[fault_handler_stderr_key].close()
-        del config.stash[fault_handler_stderr_key]
-    if config.stash.get(fault_handler_originally_enabled_key, False):
-        # Re-enable the faulthandler if it was originally enabled.
-        faulthandler.enable(file=get_stderr_fileno())
+    if fault_handler_stderr_fd_key in config.stash:
+        os.close(config.stash[fault_handler_stderr_fd_key])
+        del config.stash[fault_handler_stderr_fd_key]
+    # Re-enable the faulthandler if it was originally enabled.
+    if fault_handler_original_stderr_fd_key in config.stash:
+        faulthandler.enable(config.stash[fault_handler_original_stderr_fd_key])
+        del config.stash[fault_handler_original_stderr_fd_key]
 
 
 def get_stderr_fileno() -> int:
@@ -53,10 +60,11 @@ def get_stderr_fileno() -> int:
         if fileno == -1:
             raise AttributeError()
         return fileno
-    except (AttributeError, io.UnsupportedOperation):
+    except (AttributeError, ValueError):
         # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file.
         # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors
         # This is potentially dangerous, but the best we can do.
+        assert sys.__stderr__ is not None
         return sys.__stderr__.fileno()
 
 
@@ -64,20 +72,20 @@ def get_timeout_config_value(config: Config) -> float:
     return float(config.getini("faulthandler_timeout") or 0.0)
 
 
-@pytest.hookimpl(hookwrapper=True, trylast=True)
-def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
+@pytest.hookimpl(wrapper=True, trylast=True)
+def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
     timeout = get_timeout_config_value(item.config)
-    stderr = item.config.stash[fault_handler_stderr_key]
-    if timeout > 0 and stderr is not None:
+    if timeout > 0:
         import faulthandler
 
+        stderr = item.config.stash[fault_handler_stderr_fd_key]
         faulthandler.dump_traceback_later(timeout, file=stderr)
         try:
-            yield
+            return (yield)
         finally:
             faulthandler.cancel_dump_traceback_later()
     else:
-        yield
+        return (yield)
 
 
 @pytest.hookimpl(tryfirst=True)
diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py
index fddff931c51..bb50b014dd1 100644
--- a/src/_pytest/fixtures.py
+++ b/src/_pytest/fixtures.py
@@ -1,77 +1,82 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+import abc
+from collections import defaultdict
+from collections import deque
+from collections import OrderedDict
+from collections.abc import Callable
+from collections.abc import Generator
+from collections.abc import Iterable
+from collections.abc import Iterator
+from collections.abc import Mapping
+from collections.abc import MutableMapping
+from collections.abc import Sequence
+from collections.abc import Set as AbstractSet
+import dataclasses
 import functools
 import inspect
 import os
-import sys
-import warnings
-from collections import defaultdict
-from collections import deque
-from contextlib import suppress
 from pathlib import Path
-from types import TracebackType
+import sys
+import types
 from typing import Any
-from typing import Callable
 from typing import cast
-from typing import Dict
-from typing import Generator
+from typing import Final
+from typing import final
 from typing import Generic
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import MutableMapping
+from typing import NoReturn
 from typing import Optional
 from typing import overload
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
 from typing import TYPE_CHECKING
 from typing import TypeVar
 from typing import Union
-
-import attr
+import warnings
 
 import _pytest
 from _pytest import nodes
 from _pytest._code import getfslineno
+from _pytest._code import Source
 from _pytest._code.code import FormattedExcinfo
 from _pytest._code.code import TerminalRepr
 from _pytest._io import TerminalWriter
-from _pytest.compat import _format_args
-from _pytest.compat import _PytestWrapper
 from _pytest.compat import assert_never
-from _pytest.compat import final
 from _pytest.compat import get_real_func
-from _pytest.compat import get_real_method
 from _pytest.compat import getfuncargnames
 from _pytest.compat import getimfunc
 from _pytest.compat import getlocation
-from _pytest.compat import is_generator
 from _pytest.compat import NOTSET
+from _pytest.compat import NotSetType
 from _pytest.compat import safe_getattr
+from _pytest.compat import safe_isclass
 from _pytest.config import _PluggyPlugin
 from _pytest.config import Config
+from _pytest.config import ExitCode
 from _pytest.config.argparsing import Parser
 from _pytest.deprecated import check_ispytest
-from _pytest.deprecated import FILLFUNCARGS
+from _pytest.deprecated import MARKED_FIXTURE
 from _pytest.deprecated import YIELD_FIXTURE
+from _pytest.main import Session
 from _pytest.mark import Mark
 from _pytest.mark import ParameterSet
 from _pytest.mark.structures import MarkDecorator
 from _pytest.outcomes import fail
+from _pytest.outcomes import skip
 from _pytest.outcomes import TEST_OUTCOME
 from _pytest.pathlib import absolutepath
 from _pytest.pathlib import bestrelpath
+from _pytest.scope import _ScopeName
 from _pytest.scope import HIGH_SCOPES
 from _pytest.scope import Scope
-from _pytest.stash import StashKey
+from _pytest.warning_types import PytestRemovedIn9Warning
+from _pytest.warning_types import PytestWarning
 
 
-if TYPE_CHECKING:
-    from typing import Deque
-    from typing import NoReturn
+if sys.version_info < (3, 11):
+    from exceptiongroup import BaseExceptionGroup
+
 
-    from _pytest.scope import _ScopeName
-    from _pytest.main import Session
+if TYPE_CHECKING:
     from _pytest.python import CallSpec2
     from _pytest.python import Function
     from _pytest.python import Metafunc
@@ -83,59 +88,56 @@
 FixtureFunction = TypeVar("FixtureFunction", bound=Callable[..., object])
 # The type of a fixture function (type alias generic in fixture value).
 _FixtureFunc = Union[
-    Callable[..., FixtureValue], Callable[..., Generator[FixtureValue, None, None]]
+    Callable[..., FixtureValue], Callable[..., Generator[FixtureValue]]
 ]
 # The type of FixtureDef.cached_result (type alias generic in fixture value).
 _FixtureCachedResult = Union[
-    Tuple[
+    tuple[
         # The result.
         FixtureValue,
         # Cache key.
         object,
         None,
     ],
-    Tuple[
+    tuple[
         None,
         # Cache key.
         object,
-        # Exc info if raised.
-        Tuple[Type[BaseException], BaseException, TracebackType],
+        # The exception and the original traceback.
+        tuple[BaseException, Optional[types.TracebackType]],
     ],
 ]
 
 
-@attr.s(frozen=True, auto_attribs=True)
+@dataclasses.dataclass(frozen=True)
 class PseudoFixtureDef(Generic[FixtureValue]):
-    cached_result: "_FixtureCachedResult[FixtureValue]"
+    cached_result: _FixtureCachedResult[FixtureValue]
     _scope: Scope
 
 
-def pytest_sessionstart(session: "Session") -> None:
+def pytest_sessionstart(session: Session) -> None:
     session._fixturemanager = FixtureManager(session)
 
 
-def get_scope_package(node, fixturedef: "FixtureDef[object]"):
-    import pytest
+def get_scope_package(
+    node: nodes.Item,
+    fixturedef: FixtureDef[object],
+) -> nodes.Node | None:
+    from _pytest.python import Package
 
-    cls = pytest.Package
-    current = node
-    fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py")
-    while current and (
-        type(current) is not cls or fixture_package_name != current.nodeid
-    ):
-        current = current.parent
-    if current is None:
-        return node.session
-    return current
+    for parent in node.iter_parents():
+        if isinstance(parent, Package) and parent.nodeid == fixturedef.baseid:
+            return parent
+    return node.session
 
 
-def get_scope_node(
-    node: nodes.Node, scope: Scope
-) -> Optional[Union[nodes.Item, nodes.Collector]]:
+def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None:
     import _pytest.python
 
     if scope is Scope.Function:
-        return node.getparent(nodes.Item)
+        # Type ignored because this is actually safe, see:
+        # https://github.com/python/mypy/issues/4717
+        return node.getparent(nodes.Item)  # type: ignore[type-abstract]
     elif scope is Scope.Class:
         return node.getparent(_pytest.python.Class)
     elif scope is Scope.Module:
@@ -148,189 +150,117 @@ def get_scope_node(
         assert_never(scope)
 
 
-# Used for storing artificial fixturedefs for direct parametrization.
-name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]()
+# TODO: Try to use FixtureFunctionDefinition instead of the marker
+def getfixturemarker(obj: object) -> FixtureFunctionMarker | None:
+    """Return fixturemarker or None if it doesn't exist"""
+    if isinstance(obj, FixtureFunctionDefinition):
+        return obj._fixture_function_marker
+    return None
 
 
-def add_funcarg_pseudo_fixture_def(
-    collector: nodes.Collector, metafunc: "Metafunc", fixturemanager: "FixtureManager"
-) -> None:
-    # This function will transform all collected calls to functions
-    # if they use direct funcargs (i.e. direct parametrization)
-    # because we want later test execution to be able to rely on
-    # an existing FixtureDef structure for all arguments.
-    # XXX we can probably avoid this algorithm  if we modify CallSpec2
-    # to directly care for creating the fixturedefs within its methods.
-    if not metafunc._calls[0].funcargs:
-        # This function call does not have direct parametrization.
-        return
-    # Collect funcargs of all callspecs into a list of values.
-    arg2params: Dict[str, List[object]] = {}
-    arg2scope: Dict[str, Scope] = {}
-    for callspec in metafunc._calls:
-        for argname, argvalue in callspec.funcargs.items():
-            assert argname not in callspec.params
-            callspec.params[argname] = argvalue
-            arg2params_list = arg2params.setdefault(argname, [])
-            callspec.indices[argname] = len(arg2params_list)
-            arg2params_list.append(argvalue)
-            if argname not in arg2scope:
-                scope = callspec._arg2scope.get(argname, Scope.Function)
-                arg2scope[argname] = scope
-        callspec.funcargs.clear()
-
-    # Register artificial FixtureDef's so that later at test execution
-    # time we can rely on a proper FixtureDef to exist for fixture setup.
-    arg2fixturedefs = metafunc._arg2fixturedefs
-    for argname, valuelist in arg2params.items():
-        # If we have a scope that is higher than function, we need
-        # to make sure we only ever create an according fixturedef on
-        # a per-scope basis. We thus store and cache the fixturedef on the
-        # node related to the scope.
-        scope = arg2scope[argname]
-        node = None
-        if scope is not Scope.Function:
-            node = get_scope_node(collector, scope)
-            if node is None:
-                assert scope is Scope.Class and isinstance(
-                    collector, _pytest.python.Module
-                )
-                # Use module-level collector for class-scope (for now).
-                node = collector
-        if node is None:
-            name2pseudofixturedef = None
-        else:
-            default: Dict[str, FixtureDef[Any]] = {}
-            name2pseudofixturedef = node.stash.setdefault(
-                name2pseudofixturedef_key, default
-            )
-        if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
-            arg2fixturedefs[argname] = [name2pseudofixturedef[argname]]
-        else:
-            fixturedef = FixtureDef(
-                fixturemanager=fixturemanager,
-                baseid="",
-                argname=argname,
-                func=get_direct_param_fixture_func,
-                scope=arg2scope[argname],
-                params=valuelist,
-                unittest=False,
-                ids=None,
-            )
-            arg2fixturedefs[argname] = [fixturedef]
-            if name2pseudofixturedef is not None:
-                name2pseudofixturedef[argname] = fixturedef
+# Algorithm for sorting on a per-parametrized resource setup basis.
+# It is called for Session scope first and performs sorting
+# down to the lower scopes such as to minimize number of "high scope"
+# setups and teardowns.
 
 
-def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
-    """Return fixturemarker or None if it doesn't exist or raised
-    exceptions."""
-    try:
-        fixturemarker: Optional[FixtureFunctionMarker] = getattr(
-            obj, "_pytestfixturefunction", None
-        )
-    except TEST_OUTCOME:
-        # some objects raise errors like request (from flask import request)
-        # we don't expect them to be fixture functions
-        return None
-    return fixturemarker
+@dataclasses.dataclass(frozen=True)
+class FixtureArgKey:
+    argname: str
+    param_index: int
+    scoped_item_path: Path | None
+    item_cls: type | None
 
 
-# Parametrized fixture key, helper alias for code below.
-_Key = Tuple[object, ...]
+_V = TypeVar("_V")
+OrderedSet = dict[_V, None]
 
 
-def get_parametrized_fixture_keys(item: nodes.Item, scope: Scope) -> Iterator[_Key]:
+def get_parametrized_fixture_argkeys(
+    item: nodes.Item, scope: Scope
+) -> Iterator[FixtureArgKey]:
     """Return list of keys for all parametrized arguments which match
     the specified scope."""
     assert scope is not Scope.Function
+
     try:
-        callspec = item.callspec  # type: ignore[attr-defined]
+        callspec: CallSpec2 = item.callspec  # type: ignore[attr-defined]
     except AttributeError:
-        pass
-    else:
-        cs: CallSpec2 = callspec
-        # cs.indices.items() is random order of argnames.  Need to
-        # sort this so that different calls to
-        # get_parametrized_fixture_keys will be deterministic.
-        for argname, param_index in sorted(cs.indices.items()):
-            if cs._arg2scope[argname] != scope:
-                continue
-            if scope is Scope.Session:
-                key: _Key = (argname, param_index)
-            elif scope is Scope.Package:
-                key = (argname, param_index, item.path.parent)
-            elif scope is Scope.Module:
-                key = (argname, param_index, item.path)
-            elif scope is Scope.Class:
-                item_cls = item.cls  # type: ignore[attr-defined]
-                key = (argname, param_index, item.path, item_cls)
-            else:
-                assert_never(scope)
-            yield key
+        return
 
+    item_cls = None
+    if scope is Scope.Session:
+        scoped_item_path = None
+    elif scope is Scope.Package:
+        # Package key = module's directory.
+        scoped_item_path = item.path.parent
+    elif scope is Scope.Module:
+        scoped_item_path = item.path
+    elif scope is Scope.Class:
+        scoped_item_path = item.path
+        item_cls = item.cls  # type: ignore[attr-defined]
+    else:
+        assert_never(scope)
 
-# Algorithm for sorting on a per-parametrized resource setup basis.
-# It is called for Session scope first and performs sorting
-# down to the lower scopes such as to minimize number of "high scope"
-# setups and teardowns.
+    for argname in callspec.indices:
+        if callspec._arg2scope[argname] != scope:
+            continue
+        param_index = callspec.indices[argname]
+        yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls)
 
 
-def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
-    argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]] = {}
-    items_by_argkey: Dict[Scope, Dict[_Key, Deque[nodes.Item]]] = {}
+def reorder_items(items: Sequence[nodes.Item]) -> list[nodes.Item]:
+    argkeys_by_item: dict[Scope, dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {}
+    items_by_argkey: dict[
+        Scope, dict[FixtureArgKey, OrderedDict[nodes.Item, None]]
+    ] = {}
     for scope in HIGH_SCOPES:
-        d: Dict[nodes.Item, Dict[_Key, None]] = {}
-        argkeys_cache[scope] = d
-        item_d: Dict[_Key, Deque[nodes.Item]] = defaultdict(deque)
-        items_by_argkey[scope] = item_d
+        scoped_argkeys_by_item = argkeys_by_item[scope] = {}
+        scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(OrderedDict)
         for item in items:
-            keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None)
-            if keys:
-                d[item] = keys
-                for key in keys:
-                    item_d[key].append(item)
-    items_dict = dict.fromkeys(items, None)
+            argkeys = dict.fromkeys(get_parametrized_fixture_argkeys(item, scope))
+            if argkeys:
+                scoped_argkeys_by_item[item] = argkeys
+                for argkey in argkeys:
+                    scoped_items_by_argkey[argkey][item] = None
+
+    items_set = dict.fromkeys(items)
     return list(
-        reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, Scope.Session)
+        reorder_items_atscope(
+            items_set, argkeys_by_item, items_by_argkey, Scope.Session
+        )
     )
 
 
-def fix_cache_order(
-    item: nodes.Item,
-    argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]],
-    items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]],
-) -> None:
-    for scope in HIGH_SCOPES:
-        for key in argkeys_cache[scope].get(item, []):
-            items_by_argkey[scope][key].appendleft(item)
-
-
 def reorder_items_atscope(
-    items: Dict[nodes.Item, None],
-    argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[_Key, None]]],
-    items_by_argkey: Dict[Scope, Dict[_Key, "Deque[nodes.Item]"]],
+    items: OrderedSet[nodes.Item],
+    argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[FixtureArgKey]]],
+    items_by_argkey: Mapping[
+        Scope, Mapping[FixtureArgKey, OrderedDict[nodes.Item, None]]
+    ],
     scope: Scope,
-) -> Dict[nodes.Item, None]:
+) -> OrderedSet[nodes.Item]:
     if scope is Scope.Function or len(items) < 3:
         return items
-    ignore: Set[Optional[_Key]] = set()
-    items_deque = deque(items)
-    items_done: Dict[nodes.Item, None] = {}
+
     scoped_items_by_argkey = items_by_argkey[scope]
-    scoped_argkeys_cache = argkeys_cache[scope]
+    scoped_argkeys_by_item = argkeys_by_item[scope]
+
+    ignore: set[FixtureArgKey] = set()
+    items_deque = deque(items)
+    items_done: OrderedSet[nodes.Item] = {}
     while items_deque:
-        no_argkey_group: Dict[nodes.Item, None] = {}
+        no_argkey_items: OrderedSet[nodes.Item] = {}
         slicing_argkey = None
         while items_deque:
             item = items_deque.popleft()
-            if item in items_done or item in no_argkey_group:
+            if item in items_done or item in no_argkey_items:
                 continue
             argkeys = dict.fromkeys(
-                (k for k in scoped_argkeys_cache.get(item, []) if k not in ignore), None
+                k for k in scoped_argkeys_by_item.get(item, ()) if k not in ignore
             )
             if not argkeys:
-                no_argkey_group[item] = None
+                no_argkey_items[item] = None
             else:
                 slicing_argkey, _ = argkeys.popitem()
                 # We don't have to remove relevant items from later in the
@@ -339,68 +269,56 @@ def reorder_items_atscope(
                     i for i in scoped_items_by_argkey[slicing_argkey] if i in items
                 ]
                 for i in reversed(matching_items):
-                    fix_cache_order(i, argkeys_cache, items_by_argkey)
                     items_deque.appendleft(i)
+                    # Fix items_by_argkey order.
+                    for other_scope in HIGH_SCOPES:
+                        other_scoped_items_by_argkey = items_by_argkey[other_scope]
+                        for argkey in argkeys_by_item[other_scope].get(i, ()):
+                            other_scoped_items_by_argkey[argkey][i] = None
+                            other_scoped_items_by_argkey[argkey].move_to_end(
+                                i, last=False
+                            )
                 break
-        if no_argkey_group:
-            no_argkey_group = reorder_items_atscope(
-                no_argkey_group, argkeys_cache, items_by_argkey, scope.next_lower()
+        if no_argkey_items:
+            reordered_no_argkey_items = reorder_items_atscope(
+                no_argkey_items, argkeys_by_item, items_by_argkey, scope.next_lower()
             )
-            for item in no_argkey_group:
-                items_done[item] = None
-        ignore.add(slicing_argkey)
+            items_done.update(reordered_no_argkey_items)
+        if slicing_argkey is not None:
+            ignore.add(slicing_argkey)
     return items_done
 
 
-def _fillfuncargs(function: "Function") -> None:
-    """Fill missing fixtures for a test function, old public API (deprecated)."""
-    warnings.warn(FILLFUNCARGS.format(name="pytest._fillfuncargs()"), stacklevel=2)
-    _fill_fixtures_impl(function)
-
-
-def fillfixtures(function: "Function") -> None:
-    """Fill missing fixtures for a test function (deprecated)."""
-    warnings.warn(
-        FILLFUNCARGS.format(name="_pytest.fixtures.fillfixtures()"), stacklevel=2
-    )
-    _fill_fixtures_impl(function)
-
-
-def _fill_fixtures_impl(function: "Function") -> None:
-    """Internal implementation to fill fixtures on the given function object."""
-    try:
-        request = function._request
-    except AttributeError:
-        # XXX this special code path is only expected to execute
-        # with the oejskit plugin.  It uses classes with funcargs
-        # and we thus have to work a bit to allow this.
-        fm = function.session._fixturemanager
-        assert function.parent is not None
-        fi = fm.getfixtureinfo(function.parent, function.obj, None)
-        function._fixtureinfo = fi
-        request = function._request = FixtureRequest(function, _ispytest=True)
-        fm.session._setupstate.setup(function)
-        request._fillfixtures()
-        # Prune out funcargs for jstests.
-        function.funcargs = {name: function.funcargs[name] for name in fi.argnames}
-    else:
-        request._fillfixtures()
-
+@dataclasses.dataclass(frozen=True)
+class FuncFixtureInfo:
+    """Fixture-related information for a fixture-requesting item (e.g. test
+    function).
 
-def get_direct_param_fixture_func(request):
-    return request.param
+    This is used to examine the fixtures which an item requests statically
+    (known during collection). This includes autouse fixtures, fixtures
+    requested by the `usefixtures` marker, fixtures requested in the function
+    parameters, and the transitive closure of these.
 
+    An item may also request fixtures dynamically (using `request.getfixturevalue`);
+    these are not reflected here.
+    """
 
-@attr.s(slots=True, auto_attribs=True)
-class FuncFixtureInfo:
-    # Original function argument names.
-    argnames: Tuple[str, ...]
-    # Argnames that function immediately requires. These include argnames +
-    # fixture names specified via usefixtures and via autouse=True in fixture
-    # definitions.
-    initialnames: Tuple[str, ...]
-    names_closure: List[str]
-    name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]]
+    __slots__ = ("argnames", "initialnames", "name2fixturedefs", "names_closure")
+
+    # Fixture names that the item requests directly by function parameters.
+    argnames: tuple[str, ...]
+    # Fixture names that the item immediately requires. These include
+    # argnames + fixture names specified via usefixtures and via autouse=True in
+    # fixture definitions.
+    initialnames: tuple[str, ...]
+    # The transitive closure of the fixture names that the item requires.
+    # Note: can't include dynamic dependencies (`request.getfixturevalue` calls).
+    names_closure: list[str]
+    # A map from a fixture name in the transitive closure to the FixtureDefs
+    # matching the name which are applicable to this function.
+    # There may be multiple overriding fixtures with the same name. The
+    # sequence is ordered from furthest to closes to the function.
+    name2fixturedefs: dict[str, Sequence[FixtureDef[Any]]]
 
     def prune_dependency_tree(self) -> None:
         """Recompute names_closure from initialnames and name2fixturedefs.
@@ -413,11 +331,11 @@ def prune_dependency_tree(self) -> None:
         tree. In this way the dependency tree can get pruned, and the closure
         of argnames may get reduced.
         """
-        closure: Set[str] = set()
+        closure: set[str] = set()
         working_set = set(self.initialnames)
         while working_set:
             argname = working_set.pop()
-            # Argname may be smth not included in the original names_closure,
+            # Argname may be something not included in the original names_closure,
             # in which case we ignore it. This currently happens with pseudo
             # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'.
             # So they introduce the new dependency 'request' which might have
@@ -430,66 +348,83 @@ def prune_dependency_tree(self) -> None:
         self.names_closure[:] = sorted(closure, key=self.names_closure.index)
 
 
-class FixtureRequest:
-    """A request for a fixture from a test or fixture function.
+class FixtureRequest(abc.ABC):
+    """The type of the ``request`` fixture.
 
-    A request object gives access to the requesting test context and has
-    an optional ``param`` attribute in case the fixture is parametrized
-    indirectly.
+    A request object gives access to the requesting test context and has a
+    ``param`` attribute in case the fixture is parametrized.
     """
 
-    def __init__(self, pyfuncitem, *, _ispytest: bool = False) -> None:
+    def __init__(
+        self,
+        pyfuncitem: Function,
+        fixturename: str | None,
+        arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]],
+        fixture_defs: dict[str, FixtureDef[Any]],
+        *,
+        _ispytest: bool = False,
+    ) -> None:
         check_ispytest(_ispytest)
-        self._pyfuncitem = pyfuncitem
         #: Fixture for which this request is being performed.
-        self.fixturename: Optional[str] = None
-        self._scope = Scope.Function
-        self._fixture_defs: Dict[str, FixtureDef[Any]] = {}
-        fixtureinfo: FuncFixtureInfo = pyfuncitem._fixtureinfo
-        self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
-        self._arg2index: Dict[str, int] = {}
-        self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager
+        self.fixturename: Final = fixturename
+        self._pyfuncitem: Final = pyfuncitem
+        # The FixtureDefs for each fixture name requested by this item.
+        # Starts from the statically-known fixturedefs resolved during
+        # collection. Dynamically requested fixtures (using
+        # `request.getfixturevalue("foo")`) are added dynamically.
+        self._arg2fixturedefs: Final = arg2fixturedefs
+        # The evaluated argnames so far, mapping to the FixtureDef they resolved
+        # to.
+        self._fixture_defs: Final = fixture_defs
+        # Notes on the type of `param`:
+        # -`request.param` is only defined in parametrized fixtures, and will raise
+        #   AttributeError otherwise. Python typing has no notion of "undefined", so
+        #   this cannot be reflected in the type.
+        # - Technically `param` is only (possibly) defined on SubRequest, not
+        #   FixtureRequest, but the typing of that is still in flux so this cheats.
+        # - In the future we might consider using a generic for the param type, but
+        #   for now just using Any.
+        self.param: Any
+
+    @property
+    def _fixturemanager(self) -> FixtureManager:
+        return self._pyfuncitem.session._fixturemanager
+
+    @property
+    @abc.abstractmethod
+    def _scope(self) -> Scope:
+        raise NotImplementedError()
 
     @property
-    def scope(self) -> "_ScopeName":
+    def scope(self) -> _ScopeName:
         """Scope string, one of "function", "class", "module", "package", "session"."""
         return self._scope.value
 
+    @abc.abstractmethod
+    def _check_scope(
+        self,
+        requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
+        requested_scope: Scope,
+    ) -> None:
+        raise NotImplementedError()
+
     @property
-    def fixturenames(self) -> List[str]:
+    def fixturenames(self) -> list[str]:
         """Names of all active fixtures in this request."""
-        result = list(self._pyfuncitem._fixtureinfo.names_closure)
+        result = list(self._pyfuncitem.fixturenames)
         result.extend(set(self._fixture_defs).difference(result))
         return result
 
     @property
+    @abc.abstractmethod
     def node(self):
         """Underlying collection node (depends on current request scope)."""
-        return self._getscopeitem(self._scope)
-
-    def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]":
-        fixturedefs = self._arg2fixturedefs.get(argname, None)
-        if fixturedefs is None:
-            # We arrive here because of a dynamic call to
-            # getfixturevalue(argname) usage which was naturally
-            # not known at parsing/collection time.
-            assert self._pyfuncitem.parent is not None
-            parentid = self._pyfuncitem.parent.nodeid
-            fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid)
-            # TODO: Fix this type ignore. Either add assert or adjust types.
-            #       Can this be None here?
-            self._arg2fixturedefs[argname] = fixturedefs  # type: ignore[assignment]
-        # fixturedefs list is immutable so we maintain a decreasing index.
-        index = self._arg2index.get(argname, 0) - 1
-        if fixturedefs is None or (-index > len(fixturedefs)):
-            raise FixtureLookupError(argname, self)
-        self._arg2index[argname] = index
-        return fixturedefs[index]
+        raise NotImplementedError()
 
     @property
     def config(self) -> Config:
         """The pytest config object associated with this request."""
-        return self._pyfuncitem.config  # type: ignore[no-any-return]
+        return self._pyfuncitem.config
 
     @property
     def function(self):
@@ -512,26 +447,25 @@ def cls(self):
     @property
     def instance(self):
         """Instance (can be None) on which test function was collected."""
-        # unittest support hack, see _pytest.unittest.TestCaseFunction.
-        try:
-            return self._pyfuncitem._testcase
-        except AttributeError:
-            function = getattr(self, "function", None)
-            return getattr(function, "__self__", None)
+        if self.scope != "function":
+            return None
+        return getattr(self._pyfuncitem, "instance", None)
 
     @property
     def module(self):
         """Python module object where the test function was collected."""
         if self.scope not in ("function", "class", "module"):
             raise AttributeError(f"module not available in {self.scope}-scoped context")
-        return self._pyfuncitem.getparent(_pytest.python.Module).obj
+        mod = self._pyfuncitem.getparent(_pytest.python.Module)
+        assert mod is not None
+        return mod.obj
 
     @property
     def path(self) -> Path:
+        """Path where the test function was collected."""
         if self.scope not in ("function", "class", "module", "package"):
             raise AttributeError(f"path not available in {self.scope}-scoped context")
-        # TODO: Remove ignore once _pyfuncitem is properly typed.
-        return self._pyfuncitem.path  # type: ignore
+        return self._pyfuncitem.path
 
     @property
     def keywords(self) -> MutableMapping[str, Any]:
@@ -540,42 +474,34 @@ def keywords(self) -> MutableMapping[str, Any]:
         return node.keywords
 
     @property
-    def session(self) -> "Session":
+    def session(self) -> Session:
         """Pytest session object."""
-        return self._pyfuncitem.session  # type: ignore[no-any-return]
+        return self._pyfuncitem.session
 
+    @abc.abstractmethod
     def addfinalizer(self, finalizer: Callable[[], object]) -> None:
-        """Add finalizer/teardown function to be called after the last test
-        within the requesting test context finished execution."""
-        # XXX usually this method is shadowed by fixturedef specific ones.
-        self._addfinalizer(finalizer, scope=self.scope)
+        """Add finalizer/teardown function to be called without arguments after
+        the last test within the requesting test context finished execution."""
+        raise NotImplementedError()
 
-    def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None:
-        node = self._getscopeitem(scope)
-        node.addfinalizer(finalizer)
-
-    def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
+    def applymarker(self, marker: str | MarkDecorator) -> None:
         """Apply a marker to a single test function invocation.
 
         This method is useful if you don't want to have a keyword/marker
         on all function invocations.
 
         :param marker:
-            A :class:`pytest.MarkDecorator` object created by a call
-            to ``pytest.mark.NAME(...)``.
+            An object created by a call to ``pytest.mark.NAME(...)``.
         """
         self.node.add_marker(marker)
 
-    def raiseerror(self, msg: Optional[str]) -> "NoReturn":
-        """Raise a FixtureLookupError with the given message."""
-        raise self._fixturemanager.FixtureLookupError(None, self, msg)
+    def raiseerror(self, msg: str | None) -> NoReturn:
+        """Raise a FixtureLookupError exception.
 
-    def _fillfixtures(self) -> None:
-        item = self._pyfuncitem
-        fixturenames = getattr(item, "fixturenames", self.fixturenames)
-        for argname in fixturenames:
-            if argname not in item.funcargs:
-                item.funcargs[argname] = self.getfixturevalue(argname)
+        :param msg:
+            An optional custom error message.
+        """
+        raise FixtureLookupError(None, self, msg)
 
     def getfixturevalue(self, argname: str) -> Any:
         """Dynamically run a named fixture function.
@@ -585,226 +511,284 @@ def getfixturevalue(self, argname: str) -> Any:
         setup time, you may use this function to retrieve it inside a fixture
         or test function body.
 
+        This method can be used during the test setup phase or the test run
+        phase, but during the test teardown phase a fixture's value may not
+        be available.
+
+        :param argname:
+            The fixture name.
         :raises pytest.FixtureLookupError:
             If the given fixture could not be found.
         """
+        # Note that in addition to the use case described in the docstring,
+        # getfixturevalue() is also called by pytest itself during item and fixture
+        # setup to evaluate the fixtures that are requested statically
+        # (using function parameters, autouse, etc).
+
         fixturedef = self._get_active_fixturedef(argname)
-        assert fixturedef.cached_result is not None
+        assert fixturedef.cached_result is not None, (
+            f'The fixture value for "{argname}" is not available.  '
+            "This can happen when the fixture has already been torn down."
+        )
         return fixturedef.cached_result[0]
 
-    def _get_active_fixturedef(
-        self, argname: str
-    ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]:
-        try:
-            return self._fixture_defs[argname]
-        except KeyError:
-            try:
-                fixturedef = self._getnextfixturedef(argname)
-            except FixtureLookupError:
-                if argname == "request":
-                    cached_result = (self, [0], None)
-                    return PseudoFixtureDef(cached_result, Scope.Function)
-                raise
-        # Remove indent to prevent the python3 exception
-        # from leaking into the call.
-        self._compute_fixture_value(fixturedef)
-        self._fixture_defs[argname] = fixturedef
-        return fixturedef
+    def _iter_chain(self) -> Iterator[SubRequest]:
+        """Yield all SubRequests in the chain, from self up.
 
-    def _get_fixturestack(self) -> List["FixtureDef[Any]"]:
+        Note: does *not* yield the TopRequest.
+        """
         current = self
-        values: List[FixtureDef[Any]] = []
         while isinstance(current, SubRequest):
-            values.append(current._fixturedef)  # type: ignore[has-type]
+            yield current
             current = current._parent_request
-        values.reverse()
-        return values
 
-    def _compute_fixture_value(self, fixturedef: "FixtureDef[object]") -> None:
-        """Create a SubRequest based on "self" and call the execute method
-        of the given FixtureDef object.
+    def _get_active_fixturedef(
+        self, argname: str
+    ) -> FixtureDef[object] | PseudoFixtureDef[object]:
+        if argname == "request":
+            cached_result = (self, [0], None)
+            return PseudoFixtureDef(cached_result, Scope.Function)
+
+        # If we already finished computing a fixture by this name in this item,
+        # return it.
+        fixturedef = self._fixture_defs.get(argname)
+        if fixturedef is not None:
+            self._check_scope(fixturedef, fixturedef._scope)
+            return fixturedef
 
-        This will force the FixtureDef object to throw away any previous
-        results and compute a new fixture value, which will be stored into
-        the FixtureDef object itself.
-        """
-        # prepare a subrequest object before calling fixture function
-        # (latter managed by fixturedef)
-        argname = fixturedef.argname
-        funcitem = self._pyfuncitem
-        scope = fixturedef._scope
+        # Find the appropriate fixturedef.
+        fixturedefs = self._arg2fixturedefs.get(argname, None)
+        if fixturedefs is None:
+            # We arrive here because of a dynamic call to
+            # getfixturevalue(argname) which was naturally
+            # not known at parsing/collection time.
+            fixturedefs = self._fixturemanager.getfixturedefs(argname, self._pyfuncitem)
+            if fixturedefs is not None:
+                self._arg2fixturedefs[argname] = fixturedefs
+        # No fixtures defined with this name.
+        if fixturedefs is None:
+            raise FixtureLookupError(argname, self)
+        # The are no fixtures with this name applicable for the function.
+        if not fixturedefs:
+            raise FixtureLookupError(argname, self)
+
+        # A fixture may override another fixture with the same name, e.g. a
+        # fixture in a module can override a fixture in a conftest, a fixture in
+        # a class can override a fixture in the module, and so on.
+        # An overriding fixture can request its own name (possibly indirectly);
+        # in this case it gets the value of the fixture it overrides, one level
+        # up.
+        # Check how many `argname`s deep we are, and take the next one.
+        # `fixturedefs` is sorted from furthest to closest, so use negative
+        # indexing to go in reverse.
+        index = -1
+        for request in self._iter_chain():
+            if request.fixturename == argname:
+                index -= 1
+        # If already consumed all of the available levels, fail.
+        if -index > len(fixturedefs):
+            raise FixtureLookupError(argname, self)
+        fixturedef = fixturedefs[index]
+
+        # Prepare a SubRequest object for calling the fixture.
         try:
-            param = funcitem.callspec.getparam(argname)
-        except (AttributeError, ValueError):
+            callspec = self._pyfuncitem.callspec
+        except AttributeError:
+            callspec = None
+        if callspec is not None and argname in callspec.params:
+            param = callspec.params[argname]
+            param_index = callspec.indices[argname]
+            # The parametrize invocation scope overrides the fixture's scope.
+            scope = callspec._arg2scope[argname]
+        else:
             param = NOTSET
             param_index = 0
-            has_params = fixturedef.params is not None
-            fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
-            if has_params and fixtures_not_supported:
-                msg = (
-                    "{name} does not support fixtures, maybe unittest.TestCase subclass?\n"
-                    "Node id: {nodeid}\n"
-                    "Function type: {typename}"
-                ).format(
-                    name=funcitem.name,
-                    nodeid=funcitem.nodeid,
-                    typename=type(funcitem).__name__,
-                )
-                fail(msg, pytrace=False)
-            if has_params:
-                frame = inspect.stack()[3]
-                frameinfo = inspect.getframeinfo(frame[0])
-                source_path = absolutepath(frameinfo.filename)
-                source_lineno = frameinfo.lineno
-                try:
-                    source_path_str = str(
-                        source_path.relative_to(funcitem.config.rootpath)
-                    )
-                except ValueError:
-                    source_path_str = str(source_path)
-                msg = (
-                    "The requested fixture has no parameter defined for test:\n"
-                    "    {}\n\n"
-                    "Requested fixture '{}' defined in:\n{}"
-                    "\n\nRequested here:\n{}:{}".format(
-                        funcitem.nodeid,
-                        fixturedef.argname,
-                        getlocation(fixturedef.func, funcitem.config.rootpath),
-                        source_path_str,
-                        source_lineno,
-                    )
-                )
-                fail(msg, pytrace=False)
-        else:
-            param_index = funcitem.callspec.indices[argname]
-            # If a parametrize invocation set a scope it will override
-            # the static scope defined with the fixture function.
-            with suppress(KeyError):
-                scope = funcitem.callspec._arg2scope[argname]
-
+            scope = fixturedef._scope
+            self._check_fixturedef_without_param(fixturedef)
+        # The parametrize invocation scope only controls caching behavior while
+        # allowing wider-scoped fixtures to keep depending on the parametrized
+        # fixture. Scope control is enforced for parametrized fixtures
+        # by recreating the whole fixture tree on parameter change.
+        # Hence `fixturedef._scope`, not `scope`.
+        self._check_scope(fixturedef, fixturedef._scope)
         subrequest = SubRequest(
             self, scope, param, param_index, fixturedef, _ispytest=True
         )
 
-        # Check if a higher-level scoped fixture accesses a lower level one.
-        subrequest._check_scope(argname, self._scope, scope)
-        try:
-            # Call the fixture function.
-            fixturedef.execute(request=subrequest)
-        finally:
-            self._schedule_finalizers(fixturedef, subrequest)
+        # Make sure the fixture value is cached, running it if it isn't
+        fixturedef.execute(request=subrequest)
 
-    def _schedule_finalizers(
-        self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
-    ) -> None:
-        # If fixture function failed it might have registered finalizers.
-        subrequest.node.addfinalizer(lambda: fixturedef.finish(request=subrequest))
+        self._fixture_defs[argname] = fixturedef
+        return fixturedef
+
+    def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None:
+        """Check that this request is allowed to execute this fixturedef without
+        a param."""
+        funcitem = self._pyfuncitem
+        has_params = fixturedef.params is not None
+        fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
+        if has_params and fixtures_not_supported:
+            msg = (
+                f"{funcitem.name} does not support fixtures, maybe unittest.TestCase subclass?\n"
+                f"Node id: {funcitem.nodeid}\n"
+                f"Function type: {type(funcitem).__name__}"
+            )
+            fail(msg, pytrace=False)
+        if has_params:
+            frame = inspect.stack()[3]
+            frameinfo = inspect.getframeinfo(frame[0])
+            source_path = absolutepath(frameinfo.filename)
+            source_lineno = frameinfo.lineno
+            try:
+                source_path_str = str(source_path.relative_to(funcitem.config.rootpath))
+            except ValueError:
+                source_path_str = str(source_path)
+            location = getlocation(fixturedef.func, funcitem.config.rootpath)
+            msg = (
+                "The requested fixture has no parameter defined for test:\n"
+                f"    {funcitem.nodeid}\n\n"
+                f"Requested fixture '{fixturedef.argname}' defined in:\n"
+                f"{location}\n\n"
+                f"Requested here:\n"
+                f"{source_path_str}:{source_lineno}"
+            )
+            fail(msg, pytrace=False)
+
+    def _get_fixturestack(self) -> list[FixtureDef[Any]]:
+        values = [request._fixturedef for request in self._iter_chain()]
+        values.reverse()
+        return values
+
+
+@final
+class TopRequest(FixtureRequest):
+    """The type of the ``request`` fixture in a test function."""
+
+    def __init__(self, pyfuncitem: Function, *, _ispytest: bool = False) -> None:
+        super().__init__(
+            fixturename=None,
+            pyfuncitem=pyfuncitem,
+            arg2fixturedefs=pyfuncitem._fixtureinfo.name2fixturedefs.copy(),
+            fixture_defs={},
+            _ispytest=_ispytest,
+        )
+
+    @property
+    def _scope(self) -> Scope:
+        return Scope.Function
 
     def _check_scope(
         self,
-        argname: str,
-        invoking_scope: Scope,
+        requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
         requested_scope: Scope,
     ) -> None:
-        if argname == "request":
-            return
-        if invoking_scope > requested_scope:
-            # Try to report something helpful.
-            text = "\n".join(self._factorytraceback())
-            fail(
-                f"ScopeMismatch: You tried to access the {requested_scope.value} scoped "
-                f"fixture {argname} with a {invoking_scope.value} scoped request object, "
-                f"involved factories:\n{text}",
-                pytrace=False,
-            )
+        # TopRequest always has function scope so always valid.
+        pass
 
-    def _factorytraceback(self) -> List[str]:
-        lines = []
-        for fixturedef in self._get_fixturestack():
-            factory = fixturedef.func
-            fs, lineno = getfslineno(factory)
-            if isinstance(fs, Path):
-                session: Session = self._pyfuncitem.session
-                p = bestrelpath(session.path, fs)
-            else:
-                p = fs
-            args = _format_args(factory)
-            lines.append("%s:%d:  def %s%s" % (p, lineno + 1, factory.__name__, args))
-        return lines
-
-    def _getscopeitem(
-        self, scope: Union[Scope, "_ScopeName"]
-    ) -> Union[nodes.Item, nodes.Collector]:
-        if isinstance(scope, str):
-            scope = Scope(scope)
-        if scope is Scope.Function:
-            # This might also be a non-function Item despite its attribute name.
-            node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem
-        elif scope is Scope.Package:
-            # FIXME: _fixturedef is not defined on FixtureRequest (this class),
-            # but on FixtureRequest (a subclass).
-            node = get_scope_package(self._pyfuncitem, self._fixturedef)  # type: ignore[attr-defined]
-        else:
-            node = get_scope_node(self._pyfuncitem, scope)
-        if node is None and scope is Scope.Class:
-            # Fallback to function item itself.
-            node = self._pyfuncitem
-        assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(
-            scope, self._pyfuncitem
-        )
-        return node
+    @property
+    def node(self):
+        return self._pyfuncitem
 
     def __repr__(self) -> str:
-        return "<FixtureRequest for %r>" % (self.node)
+        return f"<FixtureRequest for {self.node!r}>"
+
+    def _fillfixtures(self) -> None:
+        item = self._pyfuncitem
+        for argname in item.fixturenames:
+            if argname not in item.funcargs:
+                item.funcargs[argname] = self.getfixturevalue(argname)
+
+    def addfinalizer(self, finalizer: Callable[[], object]) -> None:
+        self.node.addfinalizer(finalizer)
 
 
 @final
 class SubRequest(FixtureRequest):
-    """A sub request for handling getting a fixture from a test function/fixture."""
+    """The type of the ``request`` fixture in a fixture function requested
+    (transitively) by a test function."""
 
     def __init__(
         self,
-        request: "FixtureRequest",
+        request: FixtureRequest,
         scope: Scope,
         param: Any,
         param_index: int,
-        fixturedef: "FixtureDef[object]",
+        fixturedef: FixtureDef[object],
         *,
         _ispytest: bool = False,
     ) -> None:
-        check_ispytest(_ispytest)
-        self._parent_request = request
-        self.fixturename = fixturedef.argname
+        super().__init__(
+            pyfuncitem=request._pyfuncitem,
+            fixturename=fixturedef.argname,
+            fixture_defs=request._fixture_defs,
+            arg2fixturedefs=request._arg2fixturedefs,
+            _ispytest=_ispytest,
+        )
+        self._parent_request: Final[FixtureRequest] = request
+        self._scope_field: Final = scope
+        self._fixturedef: Final[FixtureDef[object]] = fixturedef
         if param is not NOTSET:
             self.param = param
-        self.param_index = param_index
-        self._scope = scope
-        self._fixturedef = fixturedef
-        self._pyfuncitem = request._pyfuncitem
-        self._fixture_defs = request._fixture_defs
-        self._arg2fixturedefs = request._arg2fixturedefs
-        self._arg2index = request._arg2index
-        self._fixturemanager = request._fixturemanager
+        self.param_index: Final = param_index
 
     def __repr__(self) -> str:
         return f"<SubRequest {self.fixturename!r} for {self._pyfuncitem!r}>"
 
-    def addfinalizer(self, finalizer: Callable[[], object]) -> None:
-        """Add finalizer/teardown function to be called after the last test
-        within the requesting test context finished execution."""
-        self._fixturedef.addfinalizer(finalizer)
+    @property
+    def _scope(self) -> Scope:
+        return self._scope_field
+
+    @property
+    def node(self):
+        scope = self._scope
+        if scope is Scope.Function:
+            # This might also be a non-function Item despite its attribute name.
+            node: nodes.Node | None = self._pyfuncitem
+        elif scope is Scope.Package:
+            node = get_scope_package(self._pyfuncitem, self._fixturedef)
+        else:
+            node = get_scope_node(self._pyfuncitem, scope)
+        if node is None and scope is Scope.Class:
+            # Fallback to function item itself.
+            node = self._pyfuncitem
+        assert node, (
+            f'Could not obtain a node for scope "{scope}" for function {self._pyfuncitem!r}'
+        )
+        return node
 
-    def _schedule_finalizers(
-        self, fixturedef: "FixtureDef[object]", subrequest: "SubRequest"
+    def _check_scope(
+        self,
+        requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object],
+        requested_scope: Scope,
     ) -> None:
-        # If the executing fixturedef was not explicitly requested in the argument list (via
-        # getfixturevalue inside the fixture call) then ensure this fixture def will be finished
-        # first.
-        if fixturedef.argname not in self.fixturenames:
-            fixturedef.addfinalizer(
-                functools.partial(self._fixturedef.finish, request=self)
+        if isinstance(requested_fixturedef, PseudoFixtureDef):
+            return
+        if self._scope > requested_scope:
+            # Try to report something helpful.
+            argname = requested_fixturedef.argname
+            fixture_stack = "\n".join(
+                self._format_fixturedef_line(fixturedef)
+                for fixturedef in self._get_fixturestack()
+            )
+            requested_fixture = self._format_fixturedef_line(requested_fixturedef)
+            fail(
+                f"ScopeMismatch: You tried to access the {requested_scope.value} scoped "
+                f"fixture {argname} with a {self._scope.value} scoped request object. "
+                f"Requesting fixture stack:\n{fixture_stack}\n"
+                f"Requested fixture:\n{requested_fixture}",
+                pytrace=False,
             )
-        super()._schedule_finalizers(fixturedef, subrequest)
+
+    def _format_fixturedef_line(self, fixturedef: FixtureDef[object]) -> str:
+        factory = fixturedef.func
+        path, lineno = getfslineno(factory)
+        if isinstance(path, Path):
+            path = bestrelpath(self._pyfuncitem.session.path, path)
+        signature = inspect.signature(factory)
+        return f"{path}:{lineno + 1}:  def {factory.__name__}{signature}"
+
+    def addfinalizer(self, finalizer: Callable[[], object]) -> None:
+        self._fixturedef.addfinalizer(finalizer)
 
 
 @final
@@ -812,19 +796,28 @@ class FixtureLookupError(LookupError):
     """Could not return a requested fixture (missing or invalid)."""
 
     def __init__(
-        self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None
+        self, argname: str | None, request: FixtureRequest, msg: str | None = None
     ) -> None:
         self.argname = argname
         self.request = request
         self.fixturestack = request._get_fixturestack()
         self.msg = msg
 
-    def formatrepr(self) -> "FixtureLookupErrorRepr":
-        tblines: List[str] = []
+    def formatrepr(self) -> FixtureLookupErrorRepr:
+        tblines: list[str] = []
         addline = tblines.append
         stack = [self.request._pyfuncitem.obj]
         stack.extend(map(lambda x: x.func, self.fixturestack))
         msg = self.msg
+        # This function currently makes an assumption that a non-None msg means we
+        # have a non-empty `self.fixturestack`. This is currently true, but if
+        # somebody at some point want to extend the use of FixtureLookupError to
+        # new cases it might break.
+        # Add the assert to make it clearer to developer that this will fail, otherwise
+        # it crashes because `fspath` does not get set due to `stack` being empty.
+        assert self.msg is None or self.fixturestack, (
+            "formatrepr assumptions broken, rewrite it to handle it"
+        )
         if msg is not None:
             # The last fixture raise an error, let's present
             # it at the requesting side.
@@ -847,14 +840,15 @@ def formatrepr(self) -> "FixtureLookupErrorRepr":
         if msg is None:
             fm = self.request._fixturemanager
             available = set()
-            parentid = self.request._pyfuncitem.parent.nodeid
+            parent = self.request._pyfuncitem.parent
+            assert parent is not None
             for name, fixturedefs in fm._arg2fixturedefs.items():
-                faclist = list(fm._matchfactories(fixturedefs, parentid))
+                faclist = list(fm._matchfactories(fixturedefs, parent))
                 if faclist:
                     available.add(name)
             if self.argname in available:
-                msg = " recursive dependency involving fixture '{}' detected".format(
-                    self.argname
+                msg = (
+                    f" recursive dependency involving fixture '{self.argname}' detected"
                 )
             else:
                 msg = f"fixture '{self.argname}' not found"
@@ -867,11 +861,11 @@ def formatrepr(self) -> "FixtureLookupErrorRepr":
 class FixtureLookupErrorRepr(TerminalRepr):
     def __init__(
         self,
-        filename: Union[str, "os.PathLike[str]"],
+        filename: str | os.PathLike[str],
         firstlineno: int,
         tblines: Sequence[str],
         errorstring: str,
-        argname: Optional[str],
+        argname: str | None,
     ) -> None:
         self.tblines = tblines
         self.errorstring = errorstring
@@ -895,23 +889,14 @@ def toterminal(self, tw: TerminalWriter) -> None:
                     red=True,
                 )
         tw.line()
-        tw.line("%s:%d" % (os.fspath(self.filename), self.firstlineno + 1))
-
-
-def fail_fixturefunc(fixturefunc, msg: str) -> "NoReturn":
-    fs, lineno = getfslineno(fixturefunc)
-    location = f"{fs}:{lineno + 1}"
-    source = _pytest._code.Source(fixturefunc)
-    fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, pytrace=False)
+        tw.line(f"{os.fspath(self.filename)}:{self.firstlineno + 1}")
 
 
 def call_fixture_func(
-    fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs
+    fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs
 ) -> FixtureValue:
-    if is_generator(fixturefunc):
-        fixturefunc = cast(
-            Callable[..., Generator[FixtureValue, None, None]], fixturefunc
-        )
+    if inspect.isgeneratorfunction(fixturefunc):
+        fixturefunc = cast(Callable[..., Generator[FixtureValue]], fixturefunc)
         generator = fixturefunc(**kwargs)
         try:
             fixture_result = next(generator)
@@ -934,29 +919,33 @@ def _teardown_yield_fixture(fixturefunc, it) -> None:
     except StopIteration:
         pass
     else:
-        fail_fixturefunc(fixturefunc, "fixture function has more than one 'yield'")
+        fs, lineno = getfslineno(fixturefunc)
+        fail(
+            f"fixture function has more than one 'yield':\n\n"
+            f"{Source(fixturefunc).indent()}\n"
+            f"{fs}:{lineno + 1}",
+            pytrace=False,
+        )
 
 
 def _eval_scope_callable(
-    scope_callable: "Callable[[str, Config], _ScopeName]",
+    scope_callable: Callable[[str, Config], _ScopeName],
     fixture_name: str,
     config: Config,
-) -> "_ScopeName":
+) -> _ScopeName:
     try:
         # Type ignored because there is no typing mechanism to specify
         # keyword arguments, currently.
         result = scope_callable(fixture_name=fixture_name, config=config)  # type: ignore[call-arg]
     except Exception as e:
         raise TypeError(
-            "Error evaluating {} while defining fixture '{}'.\n"
-            "Expected a function with the signature (*, fixture_name, config)".format(
-                scope_callable, fixture_name
-            )
+            f"Error evaluating {scope_callable} while defining fixture '{fixture_name}'.\n"
+            "Expected a function with the signature (*, fixture_name, config)"
         ) from e
     if not isinstance(result, str):
         fail(
-            "Expected {} to return a 'str' while defining fixture '{}', but it returned:\n"
-            "{!r}".format(scope_callable, fixture_name, result),
+            f"Expected {scope_callable} to return a 'str' while defining fixture '{fixture_name}', but it returned:\n"
+            f"{result!r}",
             pytrace=False,
         )
     return result
@@ -964,50 +953,76 @@ def _eval_scope_callable(
 
 @final
 class FixtureDef(Generic[FixtureValue]):
-    """A container for a factory definition."""
+    """A container for a fixture definition.
+
+    Note: At this time, only explicitly documented fields and methods are
+    considered public stable API.
+    """
 
     def __init__(
         self,
-        fixturemanager: "FixtureManager",
-        baseid: Optional[str],
+        config: Config,
+        baseid: str | None,
         argname: str,
-        func: "_FixtureFunc[FixtureValue]",
-        scope: Union[Scope, "_ScopeName", Callable[[str, Config], "_ScopeName"], None],
-        params: Optional[Sequence[object]],
-        unittest: bool = False,
-        ids: Optional[
-            Union[
-                Tuple[Union[None, str, float, int, bool], ...],
-                Callable[[Any], Optional[object]],
-            ]
-        ] = None,
+        func: _FixtureFunc[FixtureValue],
+        scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None,
+        params: Sequence[object] | None,
+        ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
+        *,
+        _ispytest: bool = False,
+        # only used in a deprecationwarning msg, can be removed in pytest9
+        _autouse: bool = False,
     ) -> None:
-        self._fixturemanager = fixturemanager
-        self.baseid = baseid or ""
-        self.has_location = baseid is not None
-        self.func = func
-        self.argname = argname
+        check_ispytest(_ispytest)
+        # The "base" node ID for the fixture.
+        #
+        # This is a node ID prefix. A fixture is only available to a node (e.g.
+        # a `Function` item) if the fixture's baseid is a nodeid of a parent of
+        # node.
+        #
+        # For a fixture found in a Collector's object (e.g. a `Module`s module,
+        # a `Class`'s class), the baseid is the Collector's nodeid.
+        #
+        # For a fixture found in a conftest plugin, the baseid is the conftest's
+        # directory path relative to the rootdir.
+        #
+        # For other plugins, the baseid is the empty string (always matches).
+        self.baseid: Final = baseid or ""
+        # Whether the fixture was found from a node or a conftest in the
+        # collection tree. Will be false for fixtures defined in non-conftest
+        # plugins.
+        self.has_location: Final = baseid is not None
+        # The fixture factory function.
+        self.func: Final = func
+        # The name by which the fixture may be requested.
+        self.argname: Final = argname
         if scope is None:
             scope = Scope.Function
         elif callable(scope):
-            scope = _eval_scope_callable(scope, argname, fixturemanager.config)
-
+            scope = _eval_scope_callable(scope, argname, config)
         if isinstance(scope, str):
             scope = Scope.from_user(
                 scope, descr=f"Fixture '{func.__name__}'", where=baseid
             )
-        self._scope = scope
-        self.params: Optional[Sequence[object]] = params
-        self.argnames: Tuple[str, ...] = getfuncargnames(
-            func, name=argname, is_method=unittest
-        )
-        self.unittest = unittest
-        self.ids = ids
-        self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
-        self._finalizers: List[Callable[[], object]] = []
+        self._scope: Final = scope
+        # If the fixture is directly parametrized, the parameter values.
+        self.params: Final = params
+        # If the fixture is directly parametrized, a tuple of explicit IDs to
+        # assign to the parameter values, or a callable to generate an ID given
+        # a parameter value.
+        self.ids: Final = ids
+        # The names requested by the fixtures.
+        self.argnames: Final = getfuncargnames(func, name=argname)
+        # If the fixture was executed, the current value of the fixture.
+        # Can change if the fixture is executed with different parameters.
+        self.cached_result: _FixtureCachedResult[FixtureValue] | None = None
+        self._finalizers: Final[list[Callable[[], object]]] = []
+
+        # only used to emit a deprecationwarning, can be removed in pytest9
+        self._autouse = _autouse
 
     @property
-    def scope(self) -> "_ScopeName":
+    def scope(self) -> _ScopeName:
         """Scope string, one of "function", "class", "module", "package", "session"."""
         return self._scope.value
 
@@ -1015,47 +1030,61 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None:
         self._finalizers.append(finalizer)
 
     def finish(self, request: SubRequest) -> None:
-        exc = None
-        try:
-            while self._finalizers:
-                try:
-                    func = self._finalizers.pop()
-                    func()
-                except BaseException as e:
-                    # XXX Only first exception will be seen by user,
-                    #     ideally all should be reported.
-                    if exc is None:
-                        exc = e
-            if exc:
-                raise exc
-        finally:
-            hook = self._fixturemanager.session.gethookproxy(request.node.path)
-            hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
-            # Even if finalization fails, we invalidate the cached fixture
-            # value and remove all finalizers because they may be bound methods
-            # which will keep instances alive.
-            self.cached_result = None
-            self._finalizers = []
+        exceptions: list[BaseException] = []
+        while self._finalizers:
+            fin = self._finalizers.pop()
+            try:
+                fin()
+            except BaseException as e:
+                exceptions.append(e)
+        node = request.node
+        node.ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
+        # Even if finalization fails, we invalidate the cached fixture
+        # value and remove all finalizers because they may be bound methods
+        # which will keep instances alive.
+        self.cached_result = None
+        self._finalizers.clear()
+        if len(exceptions) == 1:
+            raise exceptions[0]
+        elif len(exceptions) > 1:
+            msg = f'errors while tearing down fixture "{self.argname}" of {node}'
+            raise BaseExceptionGroup(msg, exceptions[::-1])
 
     def execute(self, request: SubRequest) -> FixtureValue:
-        # Get required arguments and register our own finish()
-        # with their finalization.
+        """Return the value of this fixture, executing it if not cached."""
+        # Ensure that the dependent fixtures requested by this fixture are loaded.
+        # This needs to be done before checking if we have a cached value, since
+        # if a dependent fixture has their cache invalidated, e.g. due to
+        # parametrization, they finalize themselves and fixtures depending on it
+        # (which will likely include this fixture) setting `self.cached_result = None`.
+        # See #4871
+        requested_fixtures_that_should_finalize_us = []
         for argname in self.argnames:
             fixturedef = request._get_active_fixturedef(argname)
-            if argname != "request":
-                # PseudoFixtureDef is only for "request".
-                assert isinstance(fixturedef, FixtureDef)
-                fixturedef.addfinalizer(functools.partial(self.finish, request=request))
-
-        my_cache_key = self.cache_key(request)
+            # Saves requested fixtures in a list so we later can add our finalizer
+            # to them, ensuring that if a requested fixture gets torn down we get torn
+            # down first. This is generally handled by SetupState, but still currently
+            # needed when this fixture is not parametrized but depends on a parametrized
+            # fixture.
+            if not isinstance(fixturedef, PseudoFixtureDef):
+                requested_fixtures_that_should_finalize_us.append(fixturedef)
+
+        # Check for (and return) cached value/exception.
         if self.cached_result is not None:
-            # note: comparison with `==` can fail (or be expensive) for e.g.
-            # numpy arrays (#6497).
+            request_cache_key = self.cache_key(request)
             cache_key = self.cached_result[1]
-            if my_cache_key is cache_key:
+            try:
+                # Attempt to make a normal == check: this might fail for objects
+                # which do not implement the standard comparison (like numpy arrays -- #6497).
+                cache_hit = bool(request_cache_key == cache_key)
+            except (ValueError, RuntimeError):
+                # If the comparison raises, use 'is' as fallback.
+                cache_hit = request_cache_key is cache_key
+
+            if cache_hit:
                 if self.cached_result[2] is not None:
-                    _, val, tb = self.cached_result[2]
-                    raise val.with_traceback(tb)
+                    exc, exc_tb = self.cached_result[2]
+                    raise exc.with_traceback(exc_tb)
                 else:
                     result = self.cached_result[0]
                     return result
@@ -1064,43 +1093,52 @@ def execute(self, request: SubRequest) -> FixtureValue:
             self.finish(request)
             assert self.cached_result is None
 
-        hook = self._fixturemanager.session.gethookproxy(request.node.path)
-        result = hook.pytest_fixture_setup(fixturedef=self, request=request)
+        # Add finalizer to requested fixtures we saved previously.
+        # We make sure to do this after checking for cached value to avoid
+        # adding our finalizer multiple times. (#12135)
+        finalizer = functools.partial(self.finish, request=request)
+        for parent_fixture in requested_fixtures_that_should_finalize_us:
+            parent_fixture.addfinalizer(finalizer)
+
+        ihook = request.node.ihook
+        try:
+            # Setup the fixture, run the code in it, and cache the value
+            # in self.cached_result
+            result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
+        finally:
+            # schedule our finalizer, even if the setup failed
+            request.node.addfinalizer(finalizer)
+
         return result
 
     def cache_key(self, request: SubRequest) -> object:
-        return request.param_index if not hasattr(request, "param") else request.param
+        return getattr(request, "param", None)
 
     def __repr__(self) -> str:
-        return "<FixtureDef argname={!r} scope={!r} baseid={!r}>".format(
-            self.argname, self.scope, self.baseid
-        )
+        return f"<FixtureDef argname={self.argname!r} scope={self.scope!r} baseid={self.baseid!r}>"
 
 
 def resolve_fixture_function(
     fixturedef: FixtureDef[FixtureValue], request: FixtureRequest
-) -> "_FixtureFunc[FixtureValue]":
+) -> _FixtureFunc[FixtureValue]:
     """Get the actual callable that can be called to obtain the fixture
-    value, dealing with unittest-specific instances and bound methods."""
+    value."""
     fixturefunc = fixturedef.func
-    if fixturedef.unittest:
-        if request.instance is not None:
-            # Bind the unbound method to the TestCase instance.
-            fixturefunc = fixturedef.func.__get__(request.instance)  # type: ignore[union-attr]
-    else:
-        # The fixture function needs to be bound to the actual
-        # request.instance so that code working with "fixturedef" behaves
-        # as expected.
-        if request.instance is not None:
-            # Handle the case where fixture is defined not in a test class, but some other class
-            # (for example a plugin class with a fixture), see #2270.
-            if hasattr(fixturefunc, "__self__") and not isinstance(
-                request.instance, fixturefunc.__self__.__class__  # type: ignore[union-attr]
-            ):
-                return fixturefunc
-            fixturefunc = getimfunc(fixturedef.func)
-            if fixturefunc != fixturedef.func:
-                fixturefunc = fixturefunc.__get__(request.instance)  # type: ignore[union-attr]
+    # The fixture function needs to be bound to the actual
+    # request.instance so that code working with "fixturedef" behaves
+    # as expected.
+    instance = request.instance
+    if instance is not None:
+        # Handle the case where fixture is defined not in a test class, but some other class
+        # (for example a plugin class with a fixture), see #2270.
+        if hasattr(fixturefunc, "__self__") and not isinstance(
+            instance,
+            fixturefunc.__self__.__class__,
+        ):
+            return fixturefunc
+        fixturefunc = getimfunc(fixturedef.func)
+        if fixturefunc != fixturedef.func:
+            fixturefunc = fixturefunc.__get__(instance)
     return fixturefunc
 
 
@@ -1110,166 +1148,166 @@ def pytest_fixture_setup(
     """Execution of fixture setup."""
     kwargs = {}
     for argname in fixturedef.argnames:
-        fixdef = request._get_active_fixturedef(argname)
-        assert fixdef.cached_result is not None
-        result, arg_cache_key, exc = fixdef.cached_result
-        request._check_scope(argname, request._scope, fixdef._scope)
-        kwargs[argname] = result
+        kwargs[argname] = request.getfixturevalue(argname)
 
     fixturefunc = resolve_fixture_function(fixturedef, request)
     my_cache_key = fixturedef.cache_key(request)
+
+    if inspect.isasyncgenfunction(fixturefunc) or inspect.iscoroutinefunction(
+        fixturefunc
+    ):
+        auto_str = " with autouse=True" if fixturedef._autouse else ""
+
+        warnings.warn(
+            PytestRemovedIn9Warning(
+                f"{request.node.name!r} requested an async fixture "
+                f"{request.fixturename!r}{auto_str}, with no plugin or hook that "
+                "handled it. This is usually an error, as pytest does not natively "
+                "support it. "
+                "This will turn into an error in pytest 9.\n"
+                "See: https://docs.pytest.org/en/stable/deprecations.html#sync-test-depending-on-async-fixture"
+            ),
+            # no stacklevel will point at users code, so we just point here
+            stacklevel=1,
+        )
+
     try:
         result = call_fixture_func(fixturefunc, request, kwargs)
-    except TEST_OUTCOME:
-        exc_info = sys.exc_info()
-        assert exc_info[0] is not None
-        fixturedef.cached_result = (None, my_cache_key, exc_info)
+    except TEST_OUTCOME as e:
+        if isinstance(e, skip.Exception):
+            # The test requested a fixture which caused a skip.
+            # Don't show the fixture as the skip location, as then the user
+            # wouldn't know which test skipped.
+            e._use_item_location = True
+        fixturedef.cached_result = (None, my_cache_key, (e, e.__traceback__))
         raise
     fixturedef.cached_result = (result, my_cache_key, None)
     return result
 
 
-def _ensure_immutable_ids(
-    ids: Optional[
-        Union[
-            Iterable[Union[None, str, float, int, bool]],
-            Callable[[Any], Optional[object]],
-        ]
-    ],
-) -> Optional[
-    Union[
-        Tuple[Union[None, str, float, int, bool], ...],
-        Callable[[Any], Optional[object]],
-    ]
-]:
-    if ids is None:
-        return None
-    if callable(ids):
-        return ids
-    return tuple(ids)
-
-
-def _params_converter(
-    params: Optional[Iterable[object]],
-) -> Optional[Tuple[object, ...]]:
-    return tuple(params) if params is not None else None
-
-
-def wrap_function_to_error_out_if_called_directly(
-    function: FixtureFunction,
-    fixture_marker: "FixtureFunctionMarker",
-) -> FixtureFunction:
-    """Wrap the given fixture function so we can raise an error about it being called directly,
-    instead of used as an argument in a test function."""
-    message = (
-        'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'
-        "but are created automatically when test functions request them as parameters.\n"
-        "See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n"
-        "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly about how to update your code."
-    ).format(name=fixture_marker.name or function.__name__)
-
-    @functools.wraps(function)
-    def result(*args, **kwargs):
-        fail(message, pytrace=False)
-
-    # Keep reference to the original function in our own custom attribute so we don't unwrap
-    # further than this point and lose useful wrappings like @mock.patch (#3774).
-    result.__pytest_wrapped__ = _PytestWrapper(function)  # type: ignore[attr-defined]
-
-    return cast(FixtureFunction, result)
-
-
 @final
-@attr.s(frozen=True, auto_attribs=True)
+@dataclasses.dataclass(frozen=True)
 class FixtureFunctionMarker:
-    scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]"
-    params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter)
+    scope: _ScopeName | Callable[[str, Config], _ScopeName]
+    params: tuple[object, ...] | None
     autouse: bool = False
-    ids: Union[
-        Tuple[Union[None, str, float, int, bool], ...],
-        Callable[[Any], Optional[object]],
-    ] = attr.ib(
-        default=None,
-        converter=_ensure_immutable_ids,
-    )
-    name: Optional[str] = None
+    ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None
+    name: str | None = None
+
+    _ispytest: dataclasses.InitVar[bool] = False
 
-    def __call__(self, function: FixtureFunction) -> FixtureFunction:
+    def __post_init__(self, _ispytest: bool) -> None:
+        check_ispytest(_ispytest)
+
+    def __call__(self, function: FixtureFunction) -> FixtureFunctionDefinition:
         if inspect.isclass(function):
             raise ValueError("class fixtures not supported (maybe in the future)")
 
-        if getattr(function, "_pytestfixturefunction", False):
+        if isinstance(function, FixtureFunctionDefinition):
             raise ValueError(
-                "fixture is being applied more than once to the same function"
+                f"@pytest.fixture is being applied more than once to the same function {function.__name__!r}"
             )
 
-        function = wrap_function_to_error_out_if_called_directly(function, self)
+        if hasattr(function, "pytestmark"):
+            warnings.warn(MARKED_FIXTURE, stacklevel=2)
+
+        fixture_definition = FixtureFunctionDefinition(
+            function=function, fixture_function_marker=self, _ispytest=True
+        )
 
         name = self.name or function.__name__
         if name == "request":
             location = getlocation(function)
             fail(
-                "'request' is a reserved word for fixtures, use another name:\n  {}".format(
-                    location
-                ),
+                f"'request' is a reserved word for fixtures, use another name:\n  {location}",
                 pytrace=False,
             )
 
-        # Type ignored because https://github.com/python/mypy/issues/2087.
-        function._pytestfixturefunction = self  # type: ignore[attr-defined]
-        return function
+        return fixture_definition
+
+
+# TODO: paramspec/return type annotation tracking and storing
+class FixtureFunctionDefinition:
+    def __init__(
+        self,
+        *,
+        function: Callable[..., Any],
+        fixture_function_marker: FixtureFunctionMarker,
+        instance: object | None = None,
+        _ispytest: bool = False,
+    ) -> None:
+        check_ispytest(_ispytest)
+        self.name = fixture_function_marker.name or function.__name__
+        # In order to show the function that this fixture contains in messages.
+        # Set the __name__ to be same as the function __name__ or the given fixture name.
+        self.__name__ = self.name
+        self._fixture_function_marker = fixture_function_marker
+        if instance is not None:
+            self._fixture_function = cast(
+                Callable[..., Any], function.__get__(instance)
+            )
+        else:
+            self._fixture_function = function
+        functools.update_wrapper(self, function)
+
+    def __repr__(self) -> str:
+        return f"<pytest_fixture({self._fixture_function})>"
+
+    def __get__(self, instance, owner=None):
+        """Behave like a method if the function it was applied to was a method."""
+        return FixtureFunctionDefinition(
+            function=self._fixture_function,
+            fixture_function_marker=self._fixture_function_marker,
+            instance=instance,
+            _ispytest=True,
+        )
+
+    def __call__(self, *args: Any, **kwds: Any) -> Any:
+        message = (
+            f'Fixture "{self.name}" called directly. Fixtures are not meant to be called directly,\n'
+            "but are created automatically when test functions request them as parameters.\n"
+            "See https://docs.pytest.org/en/stable/explanation/fixtures.html for more information about fixtures, and\n"
+            "https://docs.pytest.org/en/stable/deprecations.html#calling-fixtures-directly"
+        )
+        fail(message, pytrace=False)
+
+    def _get_wrapped_function(self) -> Callable[..., Any]:
+        return self._fixture_function
 
 
 @overload
 def fixture(
-    fixture_function: FixtureFunction,
+    fixture_function: Callable[..., object],
     *,
-    scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
-    params: Optional[Iterable[object]] = ...,
+    scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
+    params: Iterable[object] | None = ...,
     autouse: bool = ...,
-    ids: Optional[
-        Union[
-            Iterable[Union[None, str, float, int, bool]],
-            Callable[[Any], Optional[object]],
-        ]
-    ] = ...,
-    name: Optional[str] = ...,
-) -> FixtureFunction:
-    ...
+    ids: Sequence[object | None] | Callable[[Any], object | None] | None = ...,
+    name: str | None = ...,
+) -> FixtureFunctionDefinition: ...
 
 
 @overload
 def fixture(
     fixture_function: None = ...,
     *,
-    scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ...,
-    params: Optional[Iterable[object]] = ...,
+    scope: _ScopeName | Callable[[str, Config], _ScopeName] = ...,
+    params: Iterable[object] | None = ...,
     autouse: bool = ...,
-    ids: Optional[
-        Union[
-            Iterable[Union[None, str, float, int, bool]],
-            Callable[[Any], Optional[object]],
-        ]
-    ] = ...,
-    name: Optional[str] = None,
-) -> FixtureFunctionMarker:
-    ...
+    ids: Sequence[object | None] | Callable[[Any], object | None] | None = ...,
+    name: str | None = None,
+) -> FixtureFunctionMarker: ...
 
 
 def fixture(
-    fixture_function: Optional[FixtureFunction] = None,
+    fixture_function: FixtureFunction | None = None,
     *,
-    scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function",
-    params: Optional[Iterable[object]] = None,
+    scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function",
+    params: Iterable[object] | None = None,
     autouse: bool = False,
-    ids: Optional[
-        Union[
-            Iterable[Union[None, str, float, int, bool]],
-            Callable[[Any], Optional[object]],
-        ]
-    ] = None,
-    name: Optional[str] = None,
-) -> Union[FixtureFunctionMarker, FixtureFunction]:
+    ids: Sequence[object | None] | Callable[[Any], object | None] | None = None,
+    name: str | None = None,
+) -> FixtureFunctionMarker | FixtureFunctionDefinition:
     """Decorator to mark a fixture factory function.
 
     This decorator can be used, with or without parameters, to define a
@@ -1308,7 +1346,7 @@ def fixture(
         the fixture.
 
     :param ids:
-        List of string ids each corresponding to the params so that they are
+        Sequence of ids each corresponding to the params so that they are
         part of the test id. If no ids are provided they will be generated
         automatically from the params.
 
@@ -1322,10 +1360,11 @@ def fixture(
     """
     fixture_marker = FixtureFunctionMarker(
         scope=scope,
-        params=params,
+        params=tuple(params) if params is not None else None,
         autouse=autouse,
-        ids=ids,
+        ids=None if ids is None else ids if callable(ids) else tuple(ids),
         name=name,
+        _ispytest=True,
     )
 
     # Direct decoration.
@@ -1369,7 +1408,7 @@ def pytestconfig(request: FixtureRequest) -> Config:
     Example::
 
         def test_foo(pytestconfig):
-            if pytestconfig.getoption("verbose") > 0:
+            if pytestconfig.get_verbosity() > 0:
                 ...
 
     """
@@ -1381,8 +1420,60 @@ def pytest_addoption(parser: Parser) -> None:
         "usefixtures",
         type="args",
         default=[],
-        help="list of default fixtures to be used with this project",
+        help="List of default fixtures to be used with this project",
+    )
+    group = parser.getgroup("general")
+    group.addoption(
+        "--fixtures",
+        "--funcargs",
+        action="store_true",
+        dest="showfixtures",
+        default=False,
+        help="Show available fixtures, sorted by plugin appearance "
+        "(fixtures with leading '_' are only shown with '-v')",
     )
+    group.addoption(
+        "--fixtures-per-test",
+        action="store_true",
+        dest="show_fixtures_per_test",
+        default=False,
+        help="Show fixtures per test",
+    )
+
+
+def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
+    if config.option.showfixtures:
+        showfixtures(config)
+        return 0
+    if config.option.show_fixtures_per_test:
+        show_fixtures_per_test(config)
+        return 0
+    return None
+
+
+def _get_direct_parametrize_args(node: nodes.Node) -> set[str]:
+    """Return all direct parametrization arguments of a node, so we don't
+    mistake them for fixtures.
+
+    Check https://github.com/pytest-dev/pytest/issues/5036.
+
+    These things are done later as well when dealing with parametrization
+    so this could be improved.
+    """
+    parametrize_argnames: set[str] = set()
+    for marker in node.iter_markers(name="parametrize"):
+        if not marker.kwargs.get("indirect", False):
+            p_argnames, _ = ParameterSet._parse_parametrize_args(
+                *marker.args, **marker.kwargs
+            )
+            parametrize_argnames.update(p_argnames)
+    return parametrize_argnames
+
+
+def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]:
+    """De-duplicate the sequence of names while keeping the original order."""
+    # Ideally we would use a set, but it does not preserve insertion order.
+    return tuple(dict.fromkeys(name for seq in seqs for name in seq))
 
 
 class FixtureManager:
@@ -1416,92 +1507,105 @@ class FixtureManager:
     by a lookup of their FuncFixtureInfo.
     """
 
-    FixtureLookupError = FixtureLookupError
-    FixtureLookupErrorRepr = FixtureLookupErrorRepr
-
-    def __init__(self, session: "Session") -> None:
+    def __init__(self, session: Session) -> None:
         self.session = session
         self.config: Config = session.config
-        self._arg2fixturedefs: Dict[str, List[FixtureDef[Any]]] = {}
-        self._holderobjseen: Set[object] = set()
+        # Maps a fixture name (argname) to all of the FixtureDefs in the test
+        # suite/plugins defined with this name. Populated by parsefactories().
+        # TODO: The order of the FixtureDefs list of each arg is significant,
+        #       explain.
+        self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {}
+        self._holderobjseen: Final[set[object]] = set()
         # A mapping from a nodeid to a list of autouse fixtures it defines.
-        self._nodeid_autousenames: Dict[str, List[str]] = {
+        self._nodeid_autousenames: Final[dict[str, list[str]]] = {
             "": self.config.getini("usefixtures"),
         }
         session.config.pluginmanager.register(self, "funcmanage")
 
-    def _get_direct_parametrize_args(self, node: nodes.Node) -> List[str]:
-        """Return all direct parametrization arguments of a node, so we don't
-        mistake them for fixtures.
+    def getfixtureinfo(
+        self,
+        node: nodes.Item,
+        func: Callable[..., object] | None,
+        cls: type | None,
+    ) -> FuncFixtureInfo:
+        """Calculate the :class:`FuncFixtureInfo` for an item.
 
-        Check https://github.com/pytest-dev/pytest/issues/5036.
+        If ``func`` is None, or if the item sets an attribute
+        ``nofuncargs = True``, then ``func`` is not examined at all.
 
-        These things are done later as well when dealing with parametrization
-        so this could be improved.
+        :param node:
+            The item requesting the fixtures.
+        :param func:
+            The item's function.
+        :param cls:
+            If the function is a method, the method's class.
         """
-        parametrize_argnames: List[str] = []
-        for marker in node.iter_markers(name="parametrize"):
-            if not marker.kwargs.get("indirect", False):
-                p_argnames, _ = ParameterSet._parse_parametrize_args(
-                    *marker.args, **marker.kwargs
-                )
-                parametrize_argnames.extend(p_argnames)
-
-        return parametrize_argnames
-
-    def getfixtureinfo(
-        self, node: nodes.Node, func, cls, funcargs: bool = True
-    ) -> FuncFixtureInfo:
-        if funcargs and not getattr(node, "nofuncargs", False):
+        if func is not None and not getattr(node, "nofuncargs", False):
             argnames = getfuncargnames(func, name=node.name, cls=cls)
         else:
             argnames = ()
+        usefixturesnames = self._getusefixturesnames(node)
+        autousenames = self._getautousenames(node)
+        initialnames = deduplicate_names(autousenames, usefixturesnames, argnames)
 
-        usefixtures = tuple(
-            arg for mark in node.iter_markers(name="usefixtures") for arg in mark.args
-        )
-        initialnames = usefixtures + argnames
-        fm = node.session._fixturemanager
-        initialnames, names_closure, arg2fixturedefs = fm.getfixtureclosure(
-            initialnames, node, ignore_args=self._get_direct_parametrize_args(node)
+        direct_parametrize_args = _get_direct_parametrize_args(node)
+
+        names_closure, arg2fixturedefs = self.getfixtureclosure(
+            parentnode=node,
+            initialnames=initialnames,
+            ignore_args=direct_parametrize_args,
         )
+
         return FuncFixtureInfo(argnames, initialnames, names_closure, arg2fixturedefs)
 
-    def pytest_plugin_registered(self, plugin: _PluggyPlugin) -> None:
-        nodeid = None
-        try:
-            p = absolutepath(plugin.__file__)  # type: ignore[attr-defined]
-        except AttributeError:
-            pass
+    def pytest_plugin_registered(self, plugin: _PluggyPlugin, plugin_name: str) -> None:
+        # Fixtures defined in conftest plugins are only visible to within the
+        # conftest's directory. This is unlike fixtures in non-conftest plugins
+        # which have global visibility. So for conftests, construct the base
+        # nodeid from the plugin name (which is the conftest path).
+        if plugin_name and plugin_name.endswith("conftest.py"):
+            # Note: we explicitly do *not* use `plugin.__file__` here -- The
+            # difference is that plugin_name has the correct capitalization on
+            # case-insensitive systems (Windows) and other normalization issues
+            # (issue #11816).
+            conftestpath = absolutepath(plugin_name)
+            try:
+                nodeid = str(conftestpath.parent.relative_to(self.config.rootpath))
+            except ValueError:
+                nodeid = ""
+            if nodeid == ".":
+                nodeid = ""
+            if os.sep != nodes.SEP:
+                nodeid = nodeid.replace(os.sep, nodes.SEP)
         else:
-            # Construct the base nodeid which is later used to check
-            # what fixtures are visible for particular tests (as denoted
-            # by their test id).
-            if p.name.startswith("conftest.py"):
-                try:
-                    nodeid = str(p.parent.relative_to(self.config.rootpath))
-                except ValueError:
-                    nodeid = ""
-                if nodeid == ".":
-                    nodeid = ""
-                if os.sep != nodes.SEP:
-                    nodeid = nodeid.replace(os.sep, nodes.SEP)
+            nodeid = None
 
         self.parsefactories(plugin, nodeid)
 
-    def _getautousenames(self, nodeid: str) -> Iterator[str]:
-        """Return the names of autouse fixtures applicable to nodeid."""
-        for parentnodeid in nodes.iterparentnodeids(nodeid):
-            basenames = self._nodeid_autousenames.get(parentnodeid)
+    def _getautousenames(self, node: nodes.Node) -> Iterator[str]:
+        """Return the names of autouse fixtures applicable to node."""
+        for parentnode in node.listchain():
+            basenames = self._nodeid_autousenames.get(parentnode.nodeid)
             if basenames:
                 yield from basenames
 
+    def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]:
+        """Return the names of usefixtures fixtures applicable to node."""
+        for marker_node, mark in node.iter_markers_with_node(name="usefixtures"):
+            if not mark.args:
+                marker_node.warn(
+                    PytestWarning(
+                        f"usefixtures() in {node.nodeid} without arguments has no effect"
+                    )
+                )
+            yield from mark.args
+
     def getfixtureclosure(
         self,
-        fixturenames: Tuple[str, ...],
         parentnode: nodes.Node,
-        ignore_args: Sequence[str] = (),
-    ) -> Tuple[Tuple[str, ...], List[str], Dict[str, Sequence[FixtureDef[Any]]]]:
+        initialnames: tuple[str, ...],
+        ignore_args: AbstractSet[str],
+    ) -> tuple[list[str], dict[str, Sequence[FixtureDef[Any]]]]:
         # Collect the closure of all fixtures, starting with the given
         # fixturenames as the initial set.  As we have to visit all
         # factory definitions anyway, we also return an arg2fixturedefs
@@ -1509,22 +1613,9 @@ def getfixtureclosure(
         # to re-discover fixturedefs again for each fixturename
         # (discovering matching fixtures for a given name/node is expensive).
 
-        parentid = parentnode.nodeid
-        fixturenames_closure = list(self._getautousenames(parentid))
-
-        def merge(otherlist: Iterable[str]) -> None:
-            for arg in otherlist:
-                if arg not in fixturenames_closure:
-                    fixturenames_closure.append(arg)
-
-        merge(fixturenames)
-
-        # At this point, fixturenames_closure contains what we call "initialnames",
-        # which is a set of fixturenames the function immediately requests. We
-        # need to return it as well, so save this.
-        initialnames = tuple(fixturenames_closure)
+        fixturenames_closure = list(initialnames)
 
-        arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {}
+        arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {}
         lastlen = -1
         while lastlen != len(fixturenames_closure):
             lastlen = len(fixturenames_closure)
@@ -1533,10 +1624,12 @@ def merge(otherlist: Iterable[str]) -> None:
                     continue
                 if argname in arg2fixturedefs:
                     continue
-                fixturedefs = self.getfixturedefs(argname, parentid)
+                fixturedefs = self.getfixturedefs(argname, parentnode)
                 if fixturedefs:
                     arg2fixturedefs[argname] = fixturedefs
-                    merge(fixturedefs[-1].argnames)
+                    for arg in fixturedefs[-1].argnames:
+                        if arg not in fixturenames_closure:
+                            fixturenames_closure.append(arg)
 
         def sort_by_scope(arg_name: str) -> Scope:
             try:
@@ -1547,9 +1640,9 @@ def sort_by_scope(arg_name: str) -> Scope:
                 return fixturedefs[-1]._scope
 
         fixturenames_closure.sort(key=sort_by_scope, reverse=True)
-        return initialnames, fixturenames_closure, arg2fixturedefs
+        return fixturenames_closure, arg2fixturedefs
 
-    def pytest_generate_tests(self, metafunc: "Metafunc") -> None:
+    def pytest_generate_tests(self, metafunc: Metafunc) -> None:
         """Generate new tests based on parametrized fixtures used by the given metafunc"""
 
         def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]:
@@ -1594,93 +1687,310 @@ def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]:
 
                 # Try next super fixture, if any.
 
-    def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None:
+    def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> None:
         # Separate parametrized setups.
         items[:] = reorder_items(items)
 
+    def _register_fixture(
+        self,
+        *,
+        name: str,
+        func: _FixtureFunc[object],
+        nodeid: str | None,
+        scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] = "function",
+        params: Sequence[object] | None = None,
+        ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
+        autouse: bool = False,
+    ) -> None:
+        """Register a fixture
+
+        :param name:
+            The fixture's name.
+        :param func:
+            The fixture's implementation function.
+        :param nodeid:
+            The visibility of the fixture. The fixture will be available to the
+            node with this nodeid and its children in the collection tree.
+            None means that the fixture is visible to the entire collection tree,
+            e.g. a fixture defined for general use in a plugin.
+        :param scope:
+            The fixture's scope.
+        :param params:
+            The fixture's parametrization params.
+        :param ids:
+            The fixture's IDs.
+        :param autouse:
+            Whether this is an autouse fixture.
+        """
+        fixture_def = FixtureDef(
+            config=self.config,
+            baseid=nodeid,
+            argname=name,
+            func=func,
+            scope=scope,
+            params=params,
+            ids=ids,
+            _ispytest=True,
+            _autouse=autouse,
+        )
+
+        faclist = self._arg2fixturedefs.setdefault(name, [])
+        if fixture_def.has_location:
+            faclist.append(fixture_def)
+        else:
+            # fixturedefs with no location are at the front
+            # so this inserts the current fixturedef after the
+            # existing fixturedefs from external plugins but
+            # before the fixturedefs provided in conftests.
+            i = len([f for f in faclist if not f.has_location])
+            faclist.insert(i, fixture_def)
+        if autouse:
+            self._nodeid_autousenames.setdefault(nodeid or "", []).append(name)
+
+    @overload
+    def parsefactories(
+        self,
+        node_or_obj: nodes.Node,
+    ) -> None:
+        raise NotImplementedError()
+
+    @overload
+    def parsefactories(
+        self,
+        node_or_obj: object,
+        nodeid: str | None,
+    ) -> None:
+        raise NotImplementedError()
+
     def parsefactories(
-        self, node_or_obj, nodeid=NOTSET, unittest: bool = False
+        self,
+        node_or_obj: nodes.Node | object,
+        nodeid: str | NotSetType | None = NOTSET,
     ) -> None:
+        """Collect fixtures from a collection node or object.
+
+        Found fixtures are parsed into `FixtureDef`s and saved.
+
+        If `node_or_object` is a collection node (with an underlying Python
+        object), the node's object is traversed and the node's nodeid is used to
+        determine the fixtures' visibility. `nodeid` must not be specified in
+        this case.
+
+        If `node_or_object` is an object (e.g. a plugin), the object is
+        traversed and the given `nodeid` is used to determine the fixtures'
+        visibility. `nodeid` must be specified in this case; None and "" mean
+        total visibility.
+        """
         if nodeid is not NOTSET:
             holderobj = node_or_obj
         else:
-            holderobj = node_or_obj.obj
+            assert isinstance(node_or_obj, nodes.Node)
+            holderobj = cast(object, node_or_obj.obj)  # type: ignore[attr-defined]
+            assert isinstance(node_or_obj.nodeid, str)
             nodeid = node_or_obj.nodeid
         if holderobj in self._holderobjseen:
             return
 
+        # Avoid accessing `@property` (and other descriptors) when iterating fixtures.
+        if not safe_isclass(holderobj) and not isinstance(holderobj, types.ModuleType):
+            holderobj_tp: object = type(holderobj)
+        else:
+            holderobj_tp = holderobj
+
         self._holderobjseen.add(holderobj)
-        autousenames = []
         for name in dir(holderobj):
-            # ugly workaround for one of the fspath deprecated property of node
-            # todo: safely generalize
-            if isinstance(holderobj, nodes.Node) and name == "fspath":
-                continue
-
             # The attribute can be an arbitrary descriptor, so the attribute
-            # access below can raise. safe_getatt() ignores such exceptions.
-            obj = safe_getattr(holderobj, name, None)
-            marker = getfixturemarker(obj)
-            if not isinstance(marker, FixtureFunctionMarker):
-                # Magic globals  with __getattr__ might have got us a wrong
-                # fixture attribute.
-                continue
-
-            if marker.name:
-                name = marker.name
-
-            # During fixture definition we wrap the original fixture function
-            # to issue a warning if called directly, so here we unwrap it in
-            # order to not emit the warning when pytest itself calls the
-            # fixture function.
-            obj = get_real_method(obj, holderobj)
-
-            fixture_def = FixtureDef(
-                fixturemanager=self,
-                baseid=nodeid,
-                argname=name,
-                func=obj,
-                scope=marker.scope,
-                params=marker.params,
-                unittest=unittest,
-                ids=marker.ids,
-            )
+            # access below can raise. safe_getattr() ignores such exceptions.
+            obj_ub = safe_getattr(holderobj_tp, name, None)
+            if type(obj_ub) is FixtureFunctionDefinition:
+                marker = obj_ub._fixture_function_marker
+                if marker.name:
+                    fixture_name = marker.name
+                else:
+                    fixture_name = name
 
-            faclist = self._arg2fixturedefs.setdefault(name, [])
-            if fixture_def.has_location:
-                faclist.append(fixture_def)
-            else:
-                # fixturedefs with no location are at the front
-                # so this inserts the current fixturedef after the
-                # existing fixturedefs from external plugins but
-                # before the fixturedefs provided in conftests.
-                i = len([f for f in faclist if not f.has_location])
-                faclist.insert(i, fixture_def)
-            if marker.autouse:
-                autousenames.append(name)
-
-        if autousenames:
-            self._nodeid_autousenames.setdefault(nodeid or "", []).extend(autousenames)
+                # OK we know it is a fixture -- now safe to look up on the _instance_.
+                try:
+                    obj = getattr(holderobj, name)
+                # if the fixture is named in the decorator we cannot find it in the module
+                except AttributeError:
+                    obj = obj_ub
+
+                func = obj._get_wrapped_function()
+
+                self._register_fixture(
+                    name=fixture_name,
+                    nodeid=nodeid,
+                    func=func,
+                    scope=marker.scope,
+                    params=marker.params,
+                    ids=marker.ids,
+                    autouse=marker.autouse,
+                )
 
     def getfixturedefs(
-        self, argname: str, nodeid: str
-    ) -> Optional[Sequence[FixtureDef[Any]]]:
-        """Get a list of fixtures which are applicable to the given node id.
-
-        :param str argname: Name of the fixture to search for.
-        :param str nodeid: Full node id of the requesting test.
-        :rtype: Sequence[FixtureDef]
+        self, argname: str, node: nodes.Node
+    ) -> Sequence[FixtureDef[Any]] | None:
+        """Get FixtureDefs for a fixture name which are applicable
+        to a given node.
+
+        Returns None if there are no fixtures at all defined with the given
+        name. (This is different from the case in which there are fixtures
+        with the given name, but none applicable to the node. In this case,
+        an empty result is returned).
+
+        :param argname: Name of the fixture to search for.
+        :param node: The requesting Node.
         """
         try:
             fixturedefs = self._arg2fixturedefs[argname]
         except KeyError:
             return None
-        return tuple(self._matchfactories(fixturedefs, nodeid))
+        return tuple(self._matchfactories(fixturedefs, node))
 
     def _matchfactories(
-        self, fixturedefs: Iterable[FixtureDef[Any]], nodeid: str
+        self, fixturedefs: Iterable[FixtureDef[Any]], node: nodes.Node
     ) -> Iterator[FixtureDef[Any]]:
-        parentnodeids = set(nodes.iterparentnodeids(nodeid))
+        parentnodeids = {n.nodeid for n in node.iter_parents()}
         for fixturedef in fixturedefs:
             if fixturedef.baseid in parentnodeids:
                 yield fixturedef
+
+
+def show_fixtures_per_test(config: Config) -> int | ExitCode:
+    from _pytest.main import wrap_session
+
+    return wrap_session(config, _show_fixtures_per_test)
+
+
+_PYTEST_DIR = Path(_pytest.__file__).parent
+
+
+def _pretty_fixture_path(invocation_dir: Path, func) -> str:
+    loc = Path(getlocation(func, invocation_dir))
+    prefix = Path("...", "_pytest")
+    try:
+        return str(prefix / loc.relative_to(_PYTEST_DIR))
+    except ValueError:
+        return bestrelpath(invocation_dir, loc)
+
+
+def _show_fixtures_per_test(config: Config, session: Session) -> None:
+    import _pytest.config
+
+    session.perform_collect()
+    invocation_dir = config.invocation_params.dir
+    tw = _pytest.config.create_terminal_writer(config)
+    verbose = config.get_verbosity()
+
+    def get_best_relpath(func) -> str:
+        loc = getlocation(func, invocation_dir)
+        return bestrelpath(invocation_dir, Path(loc))
+
+    def write_fixture(fixture_def: FixtureDef[object]) -> None:
+        argname = fixture_def.argname
+        if verbose <= 0 and argname.startswith("_"):
+            return
+        prettypath = _pretty_fixture_path(invocation_dir, fixture_def.func)
+        tw.write(f"{argname}", green=True)
+        tw.write(f" -- {prettypath}", yellow=True)
+        tw.write("\n")
+        fixture_doc = inspect.getdoc(fixture_def.func)
+        if fixture_doc:
+            write_docstring(
+                tw,
+                fixture_doc.split("\n\n", maxsplit=1)[0]
+                if verbose <= 0
+                else fixture_doc,
+            )
+        else:
+            tw.line("    no docstring available", red=True)
+
+    def write_item(item: nodes.Item) -> None:
+        # Not all items have _fixtureinfo attribute.
+        info: FuncFixtureInfo | None = getattr(item, "_fixtureinfo", None)
+        if info is None or not info.name2fixturedefs:
+            # This test item does not use any fixtures.
+            return
+        tw.line()
+        tw.sep("-", f"fixtures used by {item.name}")
+        # TODO: Fix this type ignore.
+        tw.sep("-", f"({get_best_relpath(item.function)})")  # type: ignore[attr-defined]
+        # dict key not used in loop but needed for sorting.
+        for _, fixturedefs in sorted(info.name2fixturedefs.items()):
+            assert fixturedefs is not None
+            if not fixturedefs:
+                continue
+            # Last item is expected to be the one used by the test item.
+            write_fixture(fixturedefs[-1])
+
+    for session_item in session.items:
+        write_item(session_item)
+
+
+def showfixtures(config: Config) -> int | ExitCode:
+    from _pytest.main import wrap_session
+
+    return wrap_session(config, _showfixtures_main)
+
+
+def _showfixtures_main(config: Config, session: Session) -> None:
+    import _pytest.config
+
+    session.perform_collect()
+    invocation_dir = config.invocation_params.dir
+    tw = _pytest.config.create_terminal_writer(config)
+    verbose = config.get_verbosity()
+
+    fm = session._fixturemanager
+
+    available = []
+    seen: set[tuple[str, str]] = set()
+
+    for argname, fixturedefs in fm._arg2fixturedefs.items():
+        assert fixturedefs is not None
+        if not fixturedefs:
+            continue
+        for fixturedef in fixturedefs:
+            loc = getlocation(fixturedef.func, invocation_dir)
+            if (fixturedef.argname, loc) in seen:
+                continue
+            seen.add((fixturedef.argname, loc))
+            available.append(
+                (
+                    len(fixturedef.baseid),
+                    fixturedef.func.__module__,
+                    _pretty_fixture_path(invocation_dir, fixturedef.func),
+                    fixturedef.argname,
+                    fixturedef,
+                )
+            )
+
+    available.sort()
+    currentmodule = None
+    for baseid, module, prettypath, argname, fixturedef in available:
+        if currentmodule != module:
+            if not module.startswith("_pytest."):
+                tw.line()
+                tw.sep("-", f"fixtures defined from {module}")
+                currentmodule = module
+        if verbose <= 0 and argname.startswith("_"):
+            continue
+        tw.write(f"{argname}", green=True)
+        if fixturedef.scope != "function":
+            tw.write(f" [{fixturedef.scope} scope]", cyan=True)
+        tw.write(f" -- {prettypath}", yellow=True)
+        tw.write("\n")
+        doc = inspect.getdoc(fixturedef.func)
+        if doc:
+            write_docstring(
+                tw, doc.split("\n\n", maxsplit=1)[0] if verbose <= 0 else doc
+            )
+        else:
+            tw.line("    no docstring available", red=True)
+        tw.line()
+
+
+def write_docstring(tw: TerminalWriter, doc: str, indent: str = "    ") -> None:
+    for line in doc.split("\n"):
+        tw.line(indent + line)
diff --git a/src/_pytest/freeze_support.py b/src/_pytest/freeze_support.py
index 9f8ea231fed..959ff071d86 100644
--- a/src/_pytest/freeze_support.py
+++ b/src/_pytest/freeze_support.py
@@ -1,12 +1,13 @@
 """Provides a function to report all internal modules for using freezing
 tools."""
+
+from __future__ import annotations
+
+from collections.abc import Iterator
 import types
-from typing import Iterator
-from typing import List
-from typing import Union
 
 
-def freeze_includes() -> List[str]:
+def freeze_includes() -> list[str]:
     """Return a list of module names used by pytest that should be
     included by cx_freeze."""
     import _pytest
@@ -16,7 +17,7 @@ def freeze_includes() -> List[str]:
 
 
 def _iter_all_modules(
-    package: Union[str, types.ModuleType],
+    package: str | types.ModuleType,
     prefix: str = "",
 ) -> Iterator[str]:
     """Iterate over the names of all modules that can be found in the given
@@ -34,7 +35,7 @@ def _iter_all_modules(
     else:
         # Type ignored because typeshed doesn't define ModuleType.__path__
         # (only defined on packages).
-        package_path = package.__path__  # type: ignore[attr-defined]
+        package_path = package.__path__
         path, prefix = package_path[0], package.__name__ + "."
     for _, name, is_package in pkgutil.iter_modules([path]):
         if is_package:
diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py
index aca2cd391e4..b5ac0e6a50c 100644
--- a/src/_pytest/helpconfig.py
+++ b/src/_pytest/helpconfig.py
@@ -1,16 +1,19 @@
+# mypy: allow-untyped-defs
 """Version info, help messages, tracing configuration."""
+
+from __future__ import annotations
+
+from argparse import Action
+from collections.abc import Generator
 import os
 import sys
-from argparse import Action
-from typing import List
-from typing import Optional
-from typing import Union
 
-import pytest
 from _pytest.config import Config
 from _pytest.config import ExitCode
 from _pytest.config import PrintHelp
 from _pytest.config.argparsing import Parser
+from _pytest.terminal import TerminalReporter
+import pytest
 
 
 class HelpAction(Action):
@@ -49,32 +52,39 @@ def pytest_addoption(parser: Parser) -> None:
         action="count",
         default=0,
         dest="version",
-        help="display pytest version and information about plugins. "
+        help="Display pytest version and information about plugins. "
         "When given twice, also display information about plugins.",
     )
-    group._addoption(
+    group._addoption(  # private to use reserved lower-case short option
         "-h",
         "--help",
         action=HelpAction,
         dest="help",
-        help="show help message and configuration info",
+        help="Show help message and configuration info",
     )
-    group._addoption(
+    group._addoption(  # private to use reserved lower-case short option
         "-p",
         action="append",
         dest="plugins",
         default=[],
         metavar="name",
-        help="early-load given plugin module name or entry point (multi-allowed).\n"
+        help="Early-load given plugin module name or entry point (multi-allowed). "
         "To avoid loading of plugins, use the `no:` prefix, e.g. "
-        "`no:doctest`.",
+        "`no:doctest`. See also --disable-plugin-autoload.",
+    )
+    group.addoption(
+        "--disable-plugin-autoload",
+        action="store_true",
+        default=False,
+        help="Disable plugin auto-loading through entry point packaging metadata. "
+        "Only plugins explicitly specified in -p or env var PYTEST_PLUGINS will be loaded.",
     )
     group.addoption(
         "--traceconfig",
         "--trace-config",
         action="store_true",
         default=False,
-        help="trace considerations of conftest.py files.",
+        help="Trace considerations of conftest.py files",
     )
     group.addoption(
         "--debug",
@@ -83,57 +93,57 @@ def pytest_addoption(parser: Parser) -> None:
         const="pytestdebug.log",
         dest="debug",
         metavar="DEBUG_FILE_NAME",
-        help="store internal tracing debug information in this log file.\n"
-        "This file is opened with 'w' and truncated as a result, care advised.\n"
-        "Defaults to 'pytestdebug.log'.",
+        help="Store internal tracing debug information in this log file. "
+        "This file is opened with 'w' and truncated as a result, care advised. "
+        "Default: pytestdebug.log.",
     )
-    group._addoption(
+    group._addoption(  # private to use reserved lower-case short option
         "-o",
         "--override-ini",
         dest="override_ini",
         action="append",
-        help='override ini option with "option=value" style, e.g. `-o xfail_strict=True -o cache_dir=cache`.',
+        help='Override ini option with "option=value" style, '
+        "e.g. `-o xfail_strict=True -o cache_dir=cache`.",
     )
 
 
-@pytest.hookimpl(hookwrapper=True)
-def pytest_cmdline_parse():
-    outcome = yield
-    config: Config = outcome.get_result()
+@pytest.hookimpl(wrapper=True)
+def pytest_cmdline_parse() -> Generator[None, Config, Config]:
+    config = yield
 
     if config.option.debug:
         # --debug | --debug <file.log> was provided.
         path = config.option.debug
-        debugfile = open(path, "w")
+        debugfile = open(path, "w", encoding="utf-8")
         debugfile.write(
-            "versions pytest-%s, "
-            "python-%s\ncwd=%s\nargs=%s\n\n"
-            % (
+            "versions pytest-{}, "
+            "python-{}\ninvocation_dir={}\ncwd={}\nargs={}\n\n".format(
                 pytest.__version__,
                 ".".join(map(str, sys.version_info)),
+                config.invocation_params.dir,
                 os.getcwd(),
                 config.invocation_params.args,
             )
         )
         config.trace.root.setwriter(debugfile.write)
         undo_tracing = config.pluginmanager.enable_tracing()
-        sys.stderr.write("writing pytest debug information to %s\n" % path)
+        sys.stderr.write(f"writing pytest debug information to {path}\n")
 
         def unset_tracing() -> None:
             debugfile.close()
-            sys.stderr.write("wrote pytest debug information to %s\n" % debugfile.name)
+            sys.stderr.write(f"wrote pytest debug information to {debugfile.name}\n")
             config.trace.root.setwriter(None)
             undo_tracing()
 
         config.add_cleanup(unset_tracing)
 
+    return config
+
 
 def showversion(config: Config) -> None:
     if config.option.version > 1:
         sys.stdout.write(
-            "This is pytest version {}, imported from {}\n".format(
-                pytest.__version__, pytest.__file__
-            )
+            f"This is pytest version {pytest.__version__}, imported from {pytest.__file__}\n"
         )
         plugininfo = getpluginversioninfo(config)
         if plugininfo:
@@ -143,7 +153,7 @@ def showversion(config: Config) -> None:
         sys.stdout.write(f"pytest {pytest.__version__}\n")
 
 
-def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
+def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
     if config.option.version > 0:
         showversion(config)
         return 0
@@ -158,12 +168,16 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
 def showhelp(config: Config) -> None:
     import textwrap
 
-    reporter = config.pluginmanager.get_plugin("terminalreporter")
+    reporter: TerminalReporter | None = config.pluginmanager.get_plugin(
+        "terminalreporter"
+    )
+    assert reporter is not None
     tw = reporter._tw
     tw.write(config._parser.optparser.format_help())
     tw.line()
     tw.line(
-        "[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
+        "[pytest] ini-options in the first "
+        "pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:"
     )
     tw.line()
 
@@ -177,7 +191,7 @@ def showhelp(config: Config) -> None:
         if help is None:
             raise TypeError(f"help argument cannot be None for {name}")
         spec = f"{name} ({type}):"
-        tw.write("  %s" % spec)
+        tw.write(f"  {spec}")
         spec_len = len(spec)
         if spec_len > (indent_len - 3):
             # Display help starting at a new line.
@@ -203,12 +217,18 @@ def showhelp(config: Config) -> None:
                     tw.line(indent + line)
 
     tw.line()
-    tw.line("environment variables:")
+    tw.line("Environment variables:")
     vars = [
-        ("PYTEST_ADDOPTS", "extra command line options"),
-        ("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
-        ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "set to disable plugin auto-loading"),
-        ("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals"),
+        (
+            "CI",
+            "When set (regardless of value), pytest knows it is running in a "
+            "CI process and does not truncate summary info",
+        ),
+        ("BUILD_NUMBER", "Equivalent to CI"),
+        ("PYTEST_ADDOPTS", "Extra command line options"),
+        ("PYTEST_PLUGINS", "Comma-separated plugins to load during startup"),
+        ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "Set to disable plugin auto-loading"),
+        ("PYTEST_DEBUG", "Set to enable debug tracing of pytest's internals"),
     ]
     for name, help in vars:
         tw.line(f"  {name:<24} {help}")
@@ -225,17 +245,16 @@ def showhelp(config: Config) -> None:
 
     for warningreport in reporter.stats.get("warnings", []):
         tw.line("warning : " + warningreport.message, red=True)
-    return
 
 
 conftest_options = [("pytest_plugins", "list of plugin names to load")]
 
 
-def getpluginversioninfo(config: Config) -> List[str]:
+def getpluginversioninfo(config: Config) -> list[str]:
     lines = []
     plugininfo = config.pluginmanager.list_plugin_distinfo()
     if plugininfo:
-        lines.append("setuptools registered plugins:")
+        lines.append("registered third-party plugins:")
         for plugin, dist in plugininfo:
             loc = getattr(plugin, "__file__", repr(plugin))
             content = f"{dist.project_name}-{dist.version} at {loc}"
@@ -243,7 +262,7 @@ def getpluginversioninfo(config: Config) -> List[str]:
     return lines
 
 
-def pytest_report_header(config: Config) -> List[str]:
+def pytest_report_header(config: Config) -> list[str]:
     lines = []
     if config.option.debug or config.option.traceconfig:
         lines.append(f"using: pytest-{pytest.__version__}")
diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py
index b65be981560..12653ea11fe 100644
--- a/src/_pytest/hookspec.py
+++ b/src/_pytest/hookspec.py
@@ -1,32 +1,33 @@
+# mypy: allow-untyped-defs
+# ruff: noqa: T100
 """Hook specifications for pytest plugins which are invoked by pytest itself
 and by builtin plugins."""
+
+from __future__ import annotations
+
+from collections.abc import Mapping
+from collections.abc import Sequence
 from pathlib import Path
 from typing import Any
-from typing import Dict
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
 from typing import TYPE_CHECKING
-from typing import Union
 
 from pluggy import HookspecMarker
 
-from _pytest.deprecated import WARNING_CAPTURED_HOOK
-from _pytest.deprecated import WARNING_CMDLINE_PREPARSE_HOOK
+from .deprecated import HOOK_LEGACY_PATH_ARG
+
 
 if TYPE_CHECKING:
     import pdb
+    from typing import Literal
     import warnings
-    from typing_extensions import Literal
 
+    from _pytest._code.code import ExceptionInfo
     from _pytest._code.code import ExceptionRepr
-    from _pytest.code import ExceptionInfo
+    from _pytest.compat import LEGACY_PATH
+    from _pytest.config import _PluggyPlugin
     from _pytest.config import Config
     from _pytest.config import ExitCode
     from _pytest.config import PytestPluginManager
-    from _pytest.config import _PluggyPlugin
     from _pytest.config.argparsing import Parser
     from _pytest.fixtures import FixtureDef
     from _pytest.fixtures import SubRequest
@@ -34,15 +35,15 @@
     from _pytest.nodes import Collector
     from _pytest.nodes import Item
     from _pytest.outcomes import Exit
+    from _pytest.python import Class
     from _pytest.python import Function
     from _pytest.python import Metafunc
     from _pytest.python import Module
-    from _pytest.python import PyCollector
     from _pytest.reports import CollectReport
     from _pytest.reports import TestReport
     from _pytest.runner import CallInfo
     from _pytest.terminal import TerminalReporter
-    from _pytest.compat import LEGACY_PATH
+    from _pytest.terminal import TestShortLogReport
 
 
 hookspec = HookspecMarker("pytest")
@@ -53,51 +54,62 @@
 
 
 @hookspec(historic=True)
-def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None:
+def pytest_addhooks(pluginmanager: PytestPluginManager) -> None:
     """Called at plugin registration time to allow adding new hooks via a call to
-    ``pluginmanager.add_hookspecs(module_or_class, prefix)``.
+    :func:`pluginmanager.add_hookspecs(module_or_class, prefix) <pytest.PytestPluginManager.add_hookspecs>`.
 
-    :param pytest.PytestPluginManager pluginmanager: The pytest plugin manager.
+    :param pluginmanager: The pytest plugin manager.
 
     .. note::
-        This hook is incompatible with ``hookwrapper=True``.
+        This hook is incompatible with hook wrappers.
+
+    Use in conftest plugins
+    =======================
+
+    If a conftest plugin implements this hook, it will be called immediately
+    when the conftest is registered.
     """
 
 
 @hookspec(historic=True)
 def pytest_plugin_registered(
-    plugin: "_PluggyPlugin", manager: "PytestPluginManager"
+    plugin: _PluggyPlugin,
+    plugin_name: str,
+    manager: PytestPluginManager,
 ) -> None:
     """A new pytest plugin got registered.
 
     :param plugin: The plugin module or instance.
-    :param pytest.PytestPluginManager manager: pytest plugin manager.
+    :param plugin_name: The name by which the plugin is registered.
+    :param manager: The pytest plugin manager.
 
     .. note::
-        This hook is incompatible with ``hookwrapper=True``.
+        This hook is incompatible with hook wrappers.
+
+    Use in conftest plugins
+    =======================
+
+    If a conftest plugin implements this hook, it will be called immediately
+    when the conftest is registered, once for each plugin registered thus far
+    (including itself!), and for all plugins thereafter when they are
+    registered.
     """
 
 
 @hookspec(historic=True)
-def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> None:
+def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None:
     """Register argparse-style options and ini-style config values,
     called once at the beginning of a test run.
 
-    .. note::
-
-        This function should be implemented only in plugins or ``conftest.py``
-        files situated at the tests root directory due to how pytest
-        :ref:`discovers plugins during startup <pluginorder>`.
-
-    :param pytest.Parser parser:
+    :param parser:
         To add command line options, call
         :py:func:`parser.addoption(...) <pytest.Parser.addoption>`.
         To add ini-file values call :py:func:`parser.addini(...)
         <pytest.Parser.addini>`.
 
-    :param pytest.PytestPluginManager pluginmanager:
-        The pytest plugin manager, which can be used to install :py:func:`hookspec`'s
-        or :py:func:`hookimpl`'s and allow one plugin to call another plugin's hooks
+    :param pluginmanager:
+        The pytest plugin manager, which can be used to install :py:func:`~pytest.hookspec`'s
+        or :py:func:`~pytest.hookimpl`'s and allow one plugin to call another plugin's hooks
         to change how command line options are added.
 
     Options can later be accessed through the
@@ -113,24 +125,33 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") ->
     attribute or can be retrieved as the ``pytestconfig`` fixture.
 
     .. note::
-        This hook is incompatible with ``hookwrapper=True``.
+        This hook is incompatible with hook wrappers.
+
+    Use in conftest plugins
+    =======================
+
+    If a conftest plugin implements this hook, it will be called immediately
+    when the conftest is registered.
+
+    This hook is only called for :ref:`initial conftests <pluginorder>`.
     """
 
 
 @hookspec(historic=True)
-def pytest_configure(config: "Config") -> None:
+def pytest_configure(config: Config) -> None:
     """Allow plugins and conftest files to perform initial configuration.
 
-    This hook is called for every plugin and initial conftest file
-    after command line options have been parsed.
+    .. note::
+        This hook is incompatible with hook wrappers.
 
-    After that, the hook is called for other conftest files as they are
-    imported.
+    :param config: The pytest config object.
 
-    .. note::
-        This hook is incompatible with ``hookwrapper=True``.
+    Use in conftest plugins
+    =======================
 
-    :param pytest.Config config: The pytest config object.
+    This hook is called for every :ref:`initial conftest <pluginorder>` file
+    after command line options have been parsed. After that, the hook is called
+    for other conftest files as they are registered.
     """
 
 
@@ -142,60 +163,61 @@ def pytest_configure(config: "Config") -> None:
 
 @hookspec(firstresult=True)
 def pytest_cmdline_parse(
-    pluginmanager: "PytestPluginManager", args: List[str]
-) -> Optional["Config"]:
-    """Return an initialized config object, parsing the specified args.
+    pluginmanager: PytestPluginManager, args: list[str]
+) -> Config | None:
+    """Return an initialized :class:`~pytest.Config`, parsing the specified args.
 
     Stops at first non-None result, see :ref:`firstresult`.
 
     .. note::
-        This hook will only be called for plugin classes passed to the
+        This hook is only called for plugin classes passed to the
         ``plugins`` arg when using `pytest.main`_ to perform an in-process
         test run.
 
-    :param pytest.PytestPluginManager pluginmanager: The pytest plugin manager.
-    :param List[str] args: List of arguments passed on the command line.
+    :param pluginmanager: The pytest plugin manager.
+    :param args: List of arguments passed on the command line.
+    :returns: A pytest config object.
+
+    Use in conftest plugins
+    =======================
+
+    This hook is not called for conftest files.
     """
 
 
-@hookspec(warn_on_impl=WARNING_CMDLINE_PREPARSE_HOOK)
-def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None:
-    """(**Deprecated**) modify command line arguments before option parsing.
+def pytest_load_initial_conftests(
+    early_config: Config, parser: Parser, args: list[str]
+) -> None:
+    """Called to implement the loading of :ref:`initial conftest files
+    <pluginorder>` ahead of command line option parsing.
 
-    This hook is considered deprecated and will be removed in a future pytest version. Consider
-    using :func:`pytest_load_initial_conftests` instead.
+    :param early_config: The pytest config object.
+    :param args: Arguments passed on the command line.
+    :param parser: To add command line options.
 
-    .. note::
-        This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
+    Use in conftest plugins
+    =======================
 
-    :param pytest.Config config: The pytest config object.
-    :param List[str] args: Arguments passed on the command line.
+    This hook is not called for conftest files.
     """
 
 
 @hookspec(firstresult=True)
-def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]:
-    """Called for performing the main command line action. The default
-    implementation will invoke the configure hooks and runtest_mainloop.
+def pytest_cmdline_main(config: Config) -> ExitCode | int | None:
+    """Called for performing the main command line action.
 
-    Stops at first non-None result, see :ref:`firstresult`.
-
-    :param pytest.Config config: The pytest config object.
-    """
+    The default implementation will invoke the configure hooks and
+    :hook:`pytest_runtestloop`.
 
+    Stops at first non-None result, see :ref:`firstresult`.
 
-def pytest_load_initial_conftests(
-    early_config: "Config", parser: "Parser", args: List[str]
-) -> None:
-    """Called to implement the loading of initial conftest files ahead
-    of command line option parsing.
+    :param config: The pytest config object.
+    :returns: The exit code.
 
-    .. note::
-        This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
+    Use in conftest plugins
+    =======================
 
-    :param pytest.Config early_config: The pytest config object.
-    :param List[str] args: Arguments passed on the command line.
-    :param pytest.Parser parser: To add command line options.
+    This hook is only called for :ref:`initial conftests <pluginorder>`.
     """
 
 
@@ -205,7 +227,7 @@ def pytest_load_initial_conftests(
 
 
 @hookspec(firstresult=True)
-def pytest_collection(session: "Session") -> Optional[object]:
+def pytest_collection(session: Session) -> object | None:
     """Perform the collection phase for the given session.
 
     Stops at first non-None result, see :ref:`firstresult`.
@@ -237,96 +259,240 @@ def pytest_collection(session: "Session") -> Optional[object]:
     for example the terminal plugin uses it to start displaying the collection
     counter (and returns `None`).
 
-    :param pytest.Session session: The pytest session object.
+    :param session: The pytest session object.
+
+    Use in conftest plugins
+    =======================
+
+    This hook is only called for :ref:`initial conftests <pluginorder>`.
     """
 
 
 def pytest_collection_modifyitems(
-    session: "Session", config: "Config", items: List["Item"]
+    session: Session, config: Config, items: list[Item]
 ) -> None:
     """Called after collection has been performed. May filter or re-order
     the items in-place.
 
-    :param pytest.Session session: The pytest session object.
-    :param pytest.Config config: The pytest config object.
-    :param List[pytest.Item] items: List of item objects.
+    When items are deselected (filtered out from ``items``),
+    the hook :hook:`pytest_deselected` must be called explicitly
+    with the deselected items to properly notify other plugins,
+    e.g. with ``config.hook.pytest_deselected(items=deselected_items)``.
+
+    :param session: The pytest session object.
+    :param config: The pytest config object.
+    :param items: List of item objects.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest plugin can implement this hook.
     """
 
 
-def pytest_collection_finish(session: "Session") -> None:
+def pytest_collection_finish(session: Session) -> None:
     """Called after collection has been performed and modified.
 
-    :param pytest.Session session: The pytest session object.
+    :param session: The pytest session object.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest plugin can implement this hook.
     """
 
 
-@hookspec(firstresult=True)
+@hookspec(
+    firstresult=True,
+    warn_on_impl_args={
+        "path": HOOK_LEGACY_PATH_ARG.format(
+            pylib_path_arg="path", pathlib_path_arg="collection_path"
+        ),
+    },
+)
 def pytest_ignore_collect(
-    collection_path: Path, path: "LEGACY_PATH", config: "Config"
-) -> Optional[bool]:
-    """Return True to prevent considering this path for collection.
+    collection_path: Path, path: LEGACY_PATH, config: Config
+) -> bool | None:
+    """Return ``True`` to ignore this path for collection.
+
+    Return ``None`` to let other plugins ignore the path for collection.
+
+    Returning ``False`` will forcefully *not* ignore this path for collection,
+    without giving a chance for other plugins to ignore this path.
 
     This hook is consulted for all files and directories prior to calling
     more specific hooks.
 
     Stops at first non-None result, see :ref:`firstresult`.
 
-    :param pathlib.Path collection_path : The path to analyze.
-    :param LEGACY_PATH path: The path to analyze (deprecated).
-    :param pytest.Config config: The pytest config object.
+    :param collection_path: The path to analyze.
+    :type collection_path: pathlib.Path
+    :param path: The path to analyze (deprecated).
+    :param config: The pytest config object.
 
     .. versionchanged:: 7.0.0
         The ``collection_path`` parameter was added as a :class:`pathlib.Path`
         equivalent of the ``path`` parameter. The ``path`` parameter
         has been deprecated.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given collection path, only
+    conftest files in parent directories of the collection path are consulted
+    (if the path is a directory, its own conftest file is *not* consulted - a
+    directory cannot ignore itself!).
+    """
+
+
+@hookspec(firstresult=True)
+def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None:
+    """Create a :class:`~pytest.Collector` for the given directory, or None if
+    not relevant.
+
+    .. versionadded:: 8.0
+
+    For best results, the returned collector should be a subclass of
+    :class:`~pytest.Directory`, but this is not required.
+
+    The new node needs to have the specified ``parent`` as a parent.
+
+    Stops at first non-None result, see :ref:`firstresult`.
+
+    :param path: The path to analyze.
+    :type path: pathlib.Path
+
+    See :ref:`custom directory collectors` for a simple example of use of this
+    hook.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given collection path, only
+    conftest files in parent directories of the collection path are consulted
+    (if the path is a directory, its own conftest file is *not* consulted - a
+    directory cannot collect itself!).
     """
 
 
+@hookspec(
+    warn_on_impl_args={
+        "path": HOOK_LEGACY_PATH_ARG.format(
+            pylib_path_arg="path", pathlib_path_arg="file_path"
+        ),
+    },
+)
 def pytest_collect_file(
-    file_path: Path, path: "LEGACY_PATH", parent: "Collector"
-) -> "Optional[Collector]":
-    """Create a Collector for the given path, or None if not relevant.
+    file_path: Path, path: LEGACY_PATH, parent: Collector
+) -> Collector | None:
+    """Create a :class:`~pytest.Collector` for the given path, or None if not relevant.
+
+    For best results, the returned collector should be a subclass of
+    :class:`~pytest.File`, but this is not required.
 
     The new node needs to have the specified ``parent`` as a parent.
 
-    :param pathlib.Path file_path: The path to analyze.
-    :param LEGACY_PATH path: The path to collect (deprecated).
+    :param file_path: The path to analyze.
+    :type file_path: pathlib.Path
+    :param path: The path to collect (deprecated).
 
     .. versionchanged:: 7.0.0
         The ``file_path`` parameter was added as a :class:`pathlib.Path`
         equivalent of the ``path`` parameter. The ``path`` parameter
         has been deprecated.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given file path, only
+    conftest files in parent directories of the file path are consulted.
     """
 
 
 # logging hooks for collection
 
 
-def pytest_collectstart(collector: "Collector") -> None:
-    """Collector starts collecting."""
+def pytest_collectstart(collector: Collector) -> None:
+    """Collector starts collecting.
+
+    :param collector:
+        The collector.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given collector, only
+    conftest files in the collector's directory and its parent directories are
+    consulted.
+    """
+
+
+def pytest_itemcollected(item: Item) -> None:
+    """We just collected a test item.
+
+    :param item:
+        The item.
 
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
+    """
 
-def pytest_itemcollected(item: "Item") -> None:
-    """We just collected a test item."""
 
+def pytest_collectreport(report: CollectReport) -> None:
+    """Collector finished collecting.
 
-def pytest_collectreport(report: "CollectReport") -> None:
-    """Collector finished collecting."""
+    :param report:
+        The collect report.
 
+    Use in conftest plugins
+    =======================
 
-def pytest_deselected(items: Sequence["Item"]) -> None:
+    Any conftest file can implement this hook. For a given collector, only
+    conftest files in the collector's directory and its parent directories are
+    consulted.
+    """
+
+
+def pytest_deselected(items: Sequence[Item]) -> None:
     """Called for deselected test items, e.g. by keyword.
 
+    Note that this hook has two integration aspects for plugins:
+
+    - it can be *implemented* to be notified of deselected items
+    - it must be *called* from :hook:`pytest_collection_modifyitems`
+      implementations when items are deselected (to properly notify other plugins).
+
     May be called multiple times.
+
+    :param items:
+        The items.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook.
     """
 
 
 @hookspec(firstresult=True)
-def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectReport]":
+def pytest_make_collect_report(collector: Collector) -> CollectReport | None:
     """Perform :func:`collector.collect() <pytest.Collector.collect>` and return
     a :class:`~pytest.CollectReport`.
 
     Stops at first non-None result, see :ref:`firstresult`.
+
+    :param collector:
+        The collector.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given collector, only
+    conftest files in the collector's directory and its parent directories are
+    consulted.
     """
 
 
@@ -335,55 +501,105 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor
 # -------------------------------------------------------------------------
 
 
-@hookspec(firstresult=True)
+@hookspec(
+    firstresult=True,
+    warn_on_impl_args={
+        "path": HOOK_LEGACY_PATH_ARG.format(
+            pylib_path_arg="path", pathlib_path_arg="module_path"
+        ),
+    },
+)
 def pytest_pycollect_makemodule(
-    module_path: Path, path: "LEGACY_PATH", parent
-) -> Optional["Module"]:
-    """Return a Module collector or None for the given path.
+    module_path: Path, path: LEGACY_PATH, parent
+) -> Module | None:
+    """Return a :class:`pytest.Module` collector or None for the given path.
 
     This hook will be called for each matching test module path.
-    The pytest_collect_file hook needs to be used if you want to
+    The :hook:`pytest_collect_file` hook needs to be used if you want to
     create test modules for files that do not match as a test module.
 
     Stops at first non-None result, see :ref:`firstresult`.
 
-    :param pathlib.Path module_path: The path of the module to collect.
-    :param LEGACY_PATH path: The path of the module to collect (deprecated).
+    :param module_path: The path of the module to collect.
+    :type module_path: pathlib.Path
+    :param path: The path of the module to collect (deprecated).
 
     .. versionchanged:: 7.0.0
         The ``module_path`` parameter was added as a :class:`pathlib.Path`
         equivalent of the ``path`` parameter.
 
         The ``path`` parameter has been deprecated in favor of ``fspath``.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given parent collector,
+    only conftest files in the collector's directory and its parent directories
+    are consulted.
     """
 
 
 @hookspec(firstresult=True)
 def pytest_pycollect_makeitem(
-    collector: "PyCollector", name: str, obj: object
-) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]:
+    collector: Module | Class, name: str, obj: object
+) -> None | Item | Collector | list[Item | Collector]:
     """Return a custom item/collector for a Python object in a module, or None.
 
     Stops at first non-None result, see :ref:`firstresult`.
+
+    :param collector:
+        The module/class collector.
+    :param name:
+        The name of the object in the module/class.
+    :param obj:
+        The object.
+    :returns:
+        The created items/collectors.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given collector, only
+    conftest files in the collector's directory and its parent directories
+    are consulted.
     """
 
 
 @hookspec(firstresult=True)
-def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
+def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
     """Call underlying test function.
 
     Stops at first non-None result, see :ref:`firstresult`.
+
+    :param pyfuncitem:
+        The function item.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only
+    conftest files in the item's directory and its parent directories
+    are consulted.
     """
 
 
-def pytest_generate_tests(metafunc: "Metafunc") -> None:
-    """Generate (multiple) parametrized calls to a test function."""
+def pytest_generate_tests(metafunc: Metafunc) -> None:
+    """Generate (multiple) parametrized calls to a test function.
+
+    :param metafunc:
+        The :class:`~pytest.Metafunc` helper for the test function.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given function definition,
+    only conftest files in the functions's directory and its parent directories
+    are consulted.
+    """
 
 
 @hookspec(firstresult=True)
-def pytest_make_parametrize_id(
-    config: "Config", val: object, argname: str
-) -> Optional[str]:
+def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str | None:
     """Return a user-friendly string representation of the given ``val``
     that will be used by @pytest.mark.parametrize calls, or None if the hook
     doesn't know about ``val``.
@@ -392,9 +608,14 @@ def pytest_make_parametrize_id(
 
     Stops at first non-None result, see :ref:`firstresult`.
 
-    :param pytest.Config config: The pytest config object.
+    :param config: The pytest config object.
     :param val: The parametrized value.
-    :param str argname: The automatic parameter name produced by pytest.
+    :param argname: The automatic parameter name produced by pytest.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook.
     """
 
 
@@ -404,7 +625,7 @@ def pytest_make_parametrize_id(
 
 
 @hookspec(firstresult=True)
-def pytest_runtestloop(session: "Session") -> Optional[object]:
+def pytest_runtestloop(session: Session) -> object | None:
     """Perform the main runtest loop (after collection finished).
 
     The default hook implementation performs the runtest protocol for all items
@@ -417,17 +638,20 @@ def pytest_runtestloop(session: "Session") -> Optional[object]:
     If at any point ``session.shouldfail`` or ``session.shouldstop`` are set, the
     loop is terminated after the runtest protocol for the current item is finished.
 
-    :param pytest.Session session: The pytest session object.
+    :param session: The pytest session object.
 
     Stops at first non-None result, see :ref:`firstresult`.
     The return value is not used, but only stops further processing.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook.
     """
 
 
 @hookspec(firstresult=True)
-def pytest_runtest_protocol(
-    item: "Item", nextitem: "Optional[Item]"
-) -> Optional[object]:
+def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None:
     """Perform the runtest protocol for a single test item.
 
     The default runtest protocol is this (see individual hooks for full details):
@@ -440,7 +664,7 @@ def pytest_runtest_protocol(
         - ``pytest_runtest_logreport(report)``
         - ``pytest_exception_interact(call, report)`` if an interactive exception occurred
 
-    - Call phase, if the the setup passed and the ``setuponly`` pytest option is not set:
+    - Call phase, if the setup passed and the ``setuponly`` pytest option is not set:
         - ``call = pytest_runtest_call(item)`` (wrapped in ``CallInfo(when="call")``)
         - ``report = pytest_runtest_makereport(item, call)``
         - ``pytest_runtest_logreport(report)``
@@ -459,51 +683,88 @@ def pytest_runtest_protocol(
 
     Stops at first non-None result, see :ref:`firstresult`.
     The return value is not used, but only stops further processing.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook.
     """
 
 
-def pytest_runtest_logstart(
-    nodeid: str, location: Tuple[str, Optional[int], str]
-) -> None:
+def pytest_runtest_logstart(nodeid: str, location: tuple[str, int | None, str]) -> None:
     """Called at the start of running the runtest protocol for a single item.
 
-    See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
+    See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
+
+    :param nodeid: Full node ID of the item.
+    :param location: A tuple of ``(filename, lineno, testname)``
+        where ``filename`` is a file path relative to ``config.rootpath``
+        and ``lineno`` is 0-based.
+
+    Use in conftest plugins
+    =======================
 
-    :param str nodeid: Full node ID of the item.
-    :param location: A tuple of ``(filename, lineno, testname)``.
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
     """
 
 
 def pytest_runtest_logfinish(
-    nodeid: str, location: Tuple[str, Optional[int], str]
+    nodeid: str, location: tuple[str, int | None, str]
 ) -> None:
     """Called at the end of running the runtest protocol for a single item.
 
-    See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
+    See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
 
-    :param str nodeid: Full node ID of the item.
-    :param location: A tuple of ``(filename, lineno, testname)``.
+    :param nodeid: Full node ID of the item.
+    :param location: A tuple of ``(filename, lineno, testname)``
+        where ``filename`` is a file path relative to ``config.rootpath``
+        and ``lineno`` is 0-based.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
     """
 
 
-def pytest_runtest_setup(item: "Item") -> None:
+def pytest_runtest_setup(item: Item) -> None:
     """Called to perform the setup phase for a test item.
 
     The default implementation runs ``setup()`` on ``item`` and all of its
     parents (which haven't been setup yet). This includes obtaining the
     values of fixtures required by the item (which haven't been obtained
     yet).
+
+    :param item:
+        The item.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
     """
 
 
-def pytest_runtest_call(item: "Item") -> None:
+def pytest_runtest_call(item: Item) -> None:
     """Called to run the test for test item (the call phase).
 
     The default implementation calls ``item.runtest()``.
+
+    :param item:
+        The item.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
     """
 
 
-def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None:
+def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None:
     """Called to perform the teardown phase for a test item.
 
     The default implementation runs the finalizers and calls ``teardown()``
@@ -511,53 +772,91 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None:
     includes running the teardown phase of fixtures required by the item (if
     they go out of scope).
 
+    :param item:
+        The item.
     :param nextitem:
         The scheduled-to-be-next test item (None if no further test item is
         scheduled). This argument is used to perform exact teardowns, i.e.
         calling just enough finalizers so that nextitem only needs to call
         setup functions.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
     """
 
 
 @hookspec(firstresult=True)
-def pytest_runtest_makereport(
-    item: "Item", call: "CallInfo[None]"
-) -> Optional["TestReport"]:
+def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | None:
     """Called to create a :class:`~pytest.TestReport` for each of
     the setup, call and teardown runtest phases of a test item.
 
-    See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
+    See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
 
+    :param item: The item.
     :param call: The :class:`~pytest.CallInfo` for the phase.
 
     Stops at first non-None result, see :ref:`firstresult`.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
     """
 
 
-def pytest_runtest_logreport(report: "TestReport") -> None:
+def pytest_runtest_logreport(report: TestReport) -> None:
     """Process the :class:`~pytest.TestReport` produced for each
     of the setup, call and teardown runtest phases of an item.
 
-    See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
+    See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
     """
 
 
 @hookspec(firstresult=True)
 def pytest_report_to_serializable(
-    config: "Config",
-    report: Union["CollectReport", "TestReport"],
-) -> Optional[Dict[str, Any]]:
+    config: Config,
+    report: CollectReport | TestReport,
+) -> dict[str, Any] | None:
     """Serialize the given report object into a data structure suitable for
-    sending over the wire, e.g. converted to JSON."""
+    sending over the wire, e.g. converted to JSON.
+
+    :param config: The pytest config object.
+    :param report: The report.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. The exact details may depend
+    on the plugin which calls the hook.
+    """
 
 
 @hookspec(firstresult=True)
 def pytest_report_from_serializable(
-    config: "Config",
-    data: Dict[str, Any],
-) -> Optional[Union["CollectReport", "TestReport"]]:
+    config: Config,
+    data: dict[str, Any],
+) -> CollectReport | TestReport | None:
     """Restore a report object previously serialized with
-    :func:`pytest_report_to_serializable`."""
+    :hook:`pytest_report_to_serializable`.
+
+    :param config: The pytest config object.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. The exact details may depend
+    on the plugin which calls the hook.
+    """
 
 
 # -------------------------------------------------------------------------
@@ -567,11 +866,16 @@ def pytest_report_from_serializable(
 
 @hookspec(firstresult=True)
 def pytest_fixture_setup(
-    fixturedef: "FixtureDef[Any]", request: "SubRequest"
-) -> Optional[object]:
+    fixturedef: FixtureDef[Any], request: SubRequest
+) -> object | None:
     """Perform fixture setup execution.
 
-    :returns: The return value of the call to the fixture function.
+    :param fixturedef:
+        The fixture definition object.
+    :param request:
+        The fixture request object.
+    :returns:
+        The return value of the call to the fixture function.
 
     Stops at first non-None result, see :ref:`firstresult`.
 
@@ -579,15 +883,35 @@ def pytest_fixture_setup(
         If the fixture function returns None, other implementations of
         this hook function will continue to be called, according to the
         behavior of the :ref:`firstresult` option.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given fixture, only
+    conftest files in the fixture scope's directory and its parent directories
+    are consulted.
     """
 
 
 def pytest_fixture_post_finalizer(
-    fixturedef: "FixtureDef[Any]", request: "SubRequest"
+    fixturedef: FixtureDef[Any], request: SubRequest
 ) -> None:
     """Called after fixture teardown, but before the cache is cleared, so
     the fixture result ``fixturedef.cached_result`` is still available (not
-    ``None``)."""
+    ``None``).
+
+    :param fixturedef:
+        The fixture definition object.
+    :param request:
+        The fixture request object.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given fixture, only
+    conftest files in the fixture scope's directory and its parent directories
+    are consulted.
+    """
 
 
 # -------------------------------------------------------------------------
@@ -595,29 +919,44 @@ def pytest_fixture_post_finalizer(
 # -------------------------------------------------------------------------
 
 
-def pytest_sessionstart(session: "Session") -> None:
+def pytest_sessionstart(session: Session) -> None:
     """Called after the ``Session`` object has been created and before performing collection
     and entering the run test loop.
 
-    :param pytest.Session session: The pytest session object.
+    :param session: The pytest session object.
+
+    Use in conftest plugins
+    =======================
+
+    This hook is only called for :ref:`initial conftests <pluginorder>`.
     """
 
 
 def pytest_sessionfinish(
-    session: "Session",
-    exitstatus: Union[int, "ExitCode"],
+    session: Session,
+    exitstatus: int | ExitCode,
 ) -> None:
     """Called after whole test run finished, right before returning the exit status to the system.
 
-    :param pytest.Session session: The pytest session object.
-    :param int exitstatus: The status which pytest will return to the system.
+    :param session: The pytest session object.
+    :param exitstatus: The status which pytest will return to the system.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook.
     """
 
 
-def pytest_unconfigure(config: "Config") -> None:
+def pytest_unconfigure(config: Config) -> None:
     """Called before test process is exited.
 
-    :param pytest.Config config: The pytest config object.
+    :param config: The pytest config object.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook.
     """
 
 
@@ -627,8 +966,8 @@ def pytest_unconfigure(config: "Config") -> None:
 
 
 def pytest_assertrepr_compare(
-    config: "Config", op: str, left: object, right: object
-) -> Optional[List[str]]:
+    config: Config, op: str, left: object, right: object
+) -> list[str] | None:
     """Return explanation for comparisons in failing assert expressions.
 
     Return None for no custom explanation, otherwise return a list
@@ -636,11 +975,20 @@ def pytest_assertrepr_compare(
     *in* a string will be escaped. Note that all but the first line will
     be indented slightly, the intention is for the first line to be a summary.
 
-    :param pytest.Config config: The pytest config object.
+    :param config: The pytest config object.
+    :param op: The operator, e.g. `"=="`, `"!="`, `"not in"`.
+    :param left: The left operand.
+    :param right: The right operand.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
     """
 
 
-def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> None:
+def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None:
     """Called whenever an assertion passes.
 
     .. versionadded:: 5.0
@@ -661,10 +1009,16 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
     You need to **clean the .pyc** files in your project directory and interpreter libraries
     when enabling this option, as assertions will require to be re-written.
 
-    :param pytest.Item item: pytest item object of current test.
-    :param int lineno: Line number of the assert statement.
-    :param str orig: String with the original assertion.
-    :param str expl: String with the assert explanation.
+    :param item: pytest item object of current test.
+    :param lineno: Line number of the assert statement.
+    :param orig: String with the original assertion.
+    :param expl: String with the assert explanation.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in the item's directory and its parent directories are consulted.
     """
 
 
@@ -673,14 +1027,22 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
 # -------------------------------------------------------------------------
 
 
-def pytest_report_header(
-    config: "Config", start_path: Path, startdir: "LEGACY_PATH"
-) -> Union[str, List[str]]:
+@hookspec(
+    warn_on_impl_args={
+        "startdir": HOOK_LEGACY_PATH_ARG.format(
+            pylib_path_arg="startdir", pathlib_path_arg="start_path"
+        ),
+    },
+)
+def pytest_report_header(  # type:ignore[empty-body]
+    config: Config, start_path: Path, startdir: LEGACY_PATH
+) -> str | list[str]:
     """Return a string or list of strings to be displayed as header info for terminal reporting.
 
-    :param pytest.Config config: The pytest config object.
-    :param Path start_path: The starting dir.
-    :param LEGACY_PATH startdir: The starting dir (deprecated).
+    :param config: The pytest config object.
+    :param start_path: The starting dir.
+    :type start_path: pathlib.Path
+    :param startdir: The starting dir (deprecated).
 
     .. note::
 
@@ -689,25 +1051,31 @@ def pytest_report_header(
         If you want to have your line(s) displayed first, use
         :ref:`trylast=True <plugin-hookorder>`.
 
-    .. note::
-
-        This function should be implemented only in plugins or ``conftest.py``
-        files situated at the tests root directory due to how pytest
-        :ref:`discovers plugins during startup <pluginorder>`.
-
     .. versionchanged:: 7.0.0
         The ``start_path`` parameter was added as a :class:`pathlib.Path`
         equivalent of the ``startdir`` parameter. The ``startdir`` parameter
         has been deprecated.
+
+    Use in conftest plugins
+    =======================
+
+    This hook is only called for :ref:`initial conftests <pluginorder>`.
     """
 
 
-def pytest_report_collectionfinish(
-    config: "Config",
+@hookspec(
+    warn_on_impl_args={
+        "startdir": HOOK_LEGACY_PATH_ARG.format(
+            pylib_path_arg="startdir", pathlib_path_arg="start_path"
+        ),
+    },
+)
+def pytest_report_collectionfinish(  # type:ignore[empty-body]
+    config: Config,
     start_path: Path,
-    startdir: "LEGACY_PATH",
-    items: Sequence["Item"],
-) -> Union[str, List[str]]:
+    startdir: LEGACY_PATH,
+    items: Sequence[Item],
+) -> str | list[str]:
     """Return a string or list of strings to be displayed after collection
     has finished successfully.
 
@@ -715,9 +1083,10 @@ def pytest_report_collectionfinish(
 
     .. versionadded:: 3.2
 
-    :param pytest.Config config: The pytest config object.
-    :param Path start_path: The starting dir.
-    :param LEGACY_PATH startdir: The starting dir (deprecated).
+    :param config: The pytest config object.
+    :param start_path: The starting dir.
+    :type start_path: pathlib.Path
+    :param startdir: The starting dir (deprecated).
     :param items: List of pytest items that are going to be executed; this list should not be modified.
 
     .. note::
@@ -731,13 +1100,18 @@ def pytest_report_collectionfinish(
         The ``start_path`` parameter was added as a :class:`pathlib.Path`
         equivalent of the ``startdir`` parameter. The ``startdir`` parameter
         has been deprecated.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest plugin can implement this hook.
     """
 
 
 @hookspec(firstresult=True)
-def pytest_report_teststatus(
-    report: Union["CollectReport", "TestReport"], config: "Config"
-) -> Tuple[str, str, Union[str, Mapping[str, bool]]]:
+def pytest_report_teststatus(  # type:ignore[empty-body]
+    report: CollectReport | TestReport, config: Config
+) -> TestShortLogReport | tuple[str, str, str | tuple[str, Mapping[str, bool]]]:
     """Return result-category, shortletter and verbose word for status
     reporting.
 
@@ -756,91 +1130,75 @@ def pytest_report_teststatus(
 
     :param report: The report object whose status is to be returned.
     :param config: The pytest config object.
+    :returns: The test status.
 
     Stops at first non-None result, see :ref:`firstresult`.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest plugin can implement this hook.
     """
 
 
 def pytest_terminal_summary(
-    terminalreporter: "TerminalReporter",
-    exitstatus: "ExitCode",
-    config: "Config",
+    terminalreporter: TerminalReporter,
+    exitstatus: ExitCode,
+    config: Config,
 ) -> None:
     """Add a section to terminal summary reporting.
 
-    :param _pytest.terminal.TerminalReporter terminalreporter: The internal terminal reporter object.
-    :param int exitstatus: The exit status that will be reported back to the OS.
-    :param pytest.Config config: The pytest config object.
+    :param terminalreporter: The internal terminal reporter object.
+    :param exitstatus: The exit status that will be reported back to the OS.
+    :param config: The pytest config object.
 
     .. versionadded:: 4.2
         The ``config`` parameter.
-    """
-
-
-@hookspec(historic=True, warn_on_impl=WARNING_CAPTURED_HOOK)
-def pytest_warning_captured(
-    warning_message: "warnings.WarningMessage",
-    when: "Literal['config', 'collect', 'runtest']",
-    item: Optional["Item"],
-    location: Optional[Tuple[str, int, str]],
-) -> None:
-    """(**Deprecated**) Process a warning captured by the internal pytest warnings plugin.
-
-    .. deprecated:: 6.0
-
-    This hook is considered deprecated and will be removed in a future pytest version.
-    Use :func:`pytest_warning_recorded` instead.
-
-    :param warnings.WarningMessage warning_message:
-        The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
-        the same attributes as the parameters of :py:func:`warnings.showwarning`.
-
-    :param str when:
-        Indicates when the warning was captured. Possible values:
-
-        * ``"config"``: during pytest configuration/initialization stage.
-        * ``"collect"``: during test collection.
-        * ``"runtest"``: during test execution.
 
-    :param pytest.Item|None item:
-        The item being executed if ``when`` is ``"runtest"``, otherwise ``None``.
+    Use in conftest plugins
+    =======================
 
-    :param tuple location:
-        When available, holds information about the execution context of the captured
-        warning (filename, linenumber, function). ``function`` evaluates to <module>
-        when the execution context is at the module level.
+    Any conftest plugin can implement this hook.
     """
 
 
 @hookspec(historic=True)
 def pytest_warning_recorded(
-    warning_message: "warnings.WarningMessage",
-    when: "Literal['config', 'collect', 'runtest']",
+    warning_message: warnings.WarningMessage,
+    when: Literal["config", "collect", "runtest"],
     nodeid: str,
-    location: Optional[Tuple[str, int, str]],
+    location: tuple[str, int, str] | None,
 ) -> None:
     """Process a warning captured by the internal pytest warnings plugin.
 
-    :param warnings.WarningMessage warning_message:
-        The captured warning. This is the same object produced by :py:func:`warnings.catch_warnings`, and contains
-        the same attributes as the parameters of :py:func:`warnings.showwarning`.
+    :param warning_message:
+        The captured warning. This is the same object produced by :class:`warnings.catch_warnings`,
+        and contains the same attributes as the parameters of :py:func:`warnings.showwarning`.
 
-    :param str when:
+    :param when:
         Indicates when the warning was captured. Possible values:
 
         * ``"config"``: during pytest configuration/initialization stage.
         * ``"collect"``: during test collection.
         * ``"runtest"``: during test execution.
 
-    :param str nodeid:
-        Full id of the item.
+    :param nodeid:
+        Full id of the item. Empty string for warnings that are not specific to
+        a particular node.
 
-    :param tuple|None location:
+    :param location:
         When available, holds information about the execution context of the captured
         warning (filename, linenumber, function). ``function`` evaluates to <module>
         when the execution context is at the module level.
 
     .. versionadded:: 6.0
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. If the warning is specific to a
+    particular node, only conftest files in parent directories of the node are
+    consulted.
     """
 
 
@@ -849,7 +1207,9 @@ def pytest_warning_recorded(
 # -------------------------------------------------------------------------
 
 
-def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]:
+def pytest_markeval_namespace(  # type:ignore[empty-body]
+    config: Config,
+) -> dict[str, Any]:
     """Called when constructing the globals dictionary used for
     evaluating string conditions in xfail/skipif markers.
 
@@ -860,8 +1220,14 @@ def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]:
 
     .. versionadded:: 6.2
 
-    :param pytest.Config config: The pytest config object.
+    :param config: The pytest config object.
     :returns: A dictionary of additional globals to add.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given item, only conftest
+    files in parent directories of the item are consulted.
     """
 
 
@@ -871,58 +1237,97 @@ def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]:
 
 
 def pytest_internalerror(
-    excrepr: "ExceptionRepr",
-    excinfo: "ExceptionInfo[BaseException]",
-) -> Optional[bool]:
+    excrepr: ExceptionRepr,
+    excinfo: ExceptionInfo[BaseException],
+) -> bool | None:
     """Called for internal errors.
 
     Return True to suppress the fallback handling of printing an
     INTERNALERROR message directly to sys.stderr.
+
+    :param excrepr: The exception repr object.
+    :param excinfo: The exception info.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest plugin can implement this hook.
     """
 
 
 def pytest_keyboard_interrupt(
-    excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]",
+    excinfo: ExceptionInfo[KeyboardInterrupt | Exit],
 ) -> None:
-    """Called for keyboard interrupt."""
+    """Called for keyboard interrupt.
+
+    :param excinfo: The exception info.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest plugin can implement this hook.
+    """
 
 
 def pytest_exception_interact(
-    node: Union["Item", "Collector"],
-    call: "CallInfo[Any]",
-    report: Union["CollectReport", "TestReport"],
+    node: Item | Collector,
+    call: CallInfo[Any],
+    report: CollectReport | TestReport,
 ) -> None:
     """Called when an exception was raised which can potentially be
     interactively handled.
 
-    May be called during collection (see :py:func:`pytest_make_collect_report`),
-    in which case ``report`` is a :class:`CollectReport`.
+    May be called during collection (see :hook:`pytest_make_collect_report`),
+    in which case ``report`` is a :class:`~pytest.CollectReport`.
 
-    May be called during runtest of an item (see :py:func:`pytest_runtest_protocol`),
-    in which case ``report`` is a :class:`TestReport`.
+    May be called during runtest of an item (see :hook:`pytest_runtest_protocol`),
+    in which case ``report`` is a :class:`~pytest.TestReport`.
 
     This hook is not called if the exception that was raised is an internal
     exception like ``skip.Exception``.
+
+    :param node:
+        The item or collector.
+    :param call:
+        The call information. Contains the exception.
+    :param report:
+        The collection or test report.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest file can implement this hook. For a given node, only conftest
+    files in parent directories of the node are consulted.
     """
 
 
-def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None:
+def pytest_enter_pdb(config: Config, pdb: pdb.Pdb) -> None:
     """Called upon pdb.set_trace().
 
     Can be used by plugins to take special action just before the python
     debugger enters interactive mode.
 
-    :param pytest.Config config: The pytest config object.
-    :param pdb.Pdb pdb: The Pdb instance.
+    :param config: The pytest config object.
+    :param pdb: The Pdb instance.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest plugin can implement this hook.
     """
 
 
-def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None:
+def pytest_leave_pdb(config: Config, pdb: pdb.Pdb) -> None:
     """Called when leaving pdb (e.g. with continue after pdb.set_trace()).
 
     Can be used by plugins to take special action just after the python
     debugger leaves interactive mode.
 
-    :param pytest.Config config: The pytest config object.
-    :param pdb.Pdb pdb: The Pdb instance.
+    :param config: The pytest config object.
+    :param pdb: The Pdb instance.
+
+    Use in conftest plugins
+    =======================
+
+    Any conftest plugin can implement this hook.
     """
diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py
index 13688251f6a..d129cd295e7 100644
--- a/src/_pytest/junitxml.py
+++ b/src/_pytest/junitxml.py
@@ -1,3 +1,4 @@
+# mypy: allow-untyped-defs
 """Report test results in JUnit-XML format, for use with Jenkins and build
 integration servers.
 
@@ -6,21 +7,18 @@
 Output conforms to
 https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
 """
+
+from __future__ import annotations
+
+from collections.abc import Callable
+from datetime import datetime
+from datetime import timezone
 import functools
 import os
 import platform
 import re
 import xml.etree.ElementTree as ET
-from datetime import datetime
-from typing import Callable
-from typing import Dict
-from typing import List
-from typing import Match
-from typing import Optional
-from typing import Tuple
-from typing import Union
 
-import pytest
 from _pytest import nodes
 from _pytest import timing
 from _pytest._code.code import ExceptionRepr
@@ -32,6 +30,7 @@
 from _pytest.reports import TestReport
 from _pytest.stash import StashKey
 from _pytest.terminal import TerminalReporter
+import pytest
 
 
 xml_key = StashKey["LogXML"]()
@@ -48,18 +47,18 @@ def bin_xml_escape(arg: object) -> str:
     The idea is to escape visually for the user rather than for XML itself.
     """
 
-    def repl(matchobj: Match[str]) -> str:
+    def repl(matchobj: re.Match[str]) -> str:
         i = ord(matchobj.group())
         if i <= 0xFF:
-            return "#x%02X" % i
+            return f"#x{i:02X}"
         else:
-            return "#x%04X" % i
+            return f"#x{i:04X}"
 
     # The spec range of valid chars is:
     # Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
     # For an unknown(?) reason, we disallow #x7F (DEL) as well.
     illegal_xml_re = (
-        "[^\u0009\u000A\u000D\u0020-\u007E\u0080-\uD7FF\uE000-\uFFFD\u10000-\u10FFFF]"
+        "[^\u0009\u000a\u000d\u0020-\u007e\u0080-\ud7ff\ue000-\ufffd\u10000-\u10ffff]"
     )
     return re.sub(illegal_xml_re, repl, str(arg))
 
@@ -74,10 +73,10 @@ def merge_family(left, right) -> None:
     left.update(result)
 
 
-families = {}
-families["_base"] = {"testcase": ["classname", "name"]}
-families["_base_legacy"] = {"testcase": ["file", "line", "url"]}
-
+families = {  # pylint: disable=dict-init-mutate
+    "_base": {"testcase": ["classname", "name"]},
+    "_base_legacy": {"testcase": ["file", "line", "url"]},
+}
 # xUnit 1.x inherits legacy attributes.
 families["xunit1"] = families["_base"].copy()
 merge_family(families["xunit1"], families["_base_legacy"])
@@ -87,15 +86,15 @@ def merge_family(left, right) -> None:
 
 
 class _NodeReporter:
-    def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None:
+    def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None:
         self.id = nodeid
         self.xml = xml
         self.add_stats = self.xml.add_stats
         self.family = self.xml.family
-        self.duration = 0
-        self.properties: List[Tuple[str, str]] = []
-        self.nodes: List[ET.Element] = []
-        self.attrs: Dict[str, str] = {}
+        self.duration = 0.0
+        self.properties: list[tuple[str, str]] = []
+        self.nodes: list[ET.Element] = []
+        self.attrs: dict[str, str] = {}
 
     def append(self, node: ET.Element) -> None:
         self.xml.add_stats(node.tag)
@@ -107,7 +106,7 @@ def add_property(self, name: str, value: object) -> None:
     def add_attribute(self, name: str, value: object) -> None:
         self.attrs[str(name)] = bin_xml_escape(value)
 
-    def make_properties_node(self) -> Optional[ET.Element]:
+    def make_properties_node(self) -> ET.Element | None:
         """Return a Junit node containing custom properties, if any."""
         if self.properties:
             properties = ET.Element("properties")
@@ -122,7 +121,7 @@ def record_testreport(self, testreport: TestReport) -> None:
         classnames = names[:-1]
         if self.xml.prefix:
             classnames.insert(0, self.xml.prefix)
-        attrs: Dict[str, str] = {
+        attrs: dict[str, str] = {
             "classname": ".".join(classnames),
             "name": bin_xml_escape(names[-1]),
             "file": testreport.location[0],
@@ -141,20 +140,20 @@ def record_testreport(self, testreport: TestReport) -> None:
         # Filter out attributes not permitted by this test family.
         # Including custom attributes because they are not valid here.
         temp_attrs = {}
-        for key in self.attrs.keys():
+        for key in self.attrs:
             if key in families[self.family]["testcase"]:
                 temp_attrs[key] = self.attrs[key]
         self.attrs = temp_attrs
 
     def to_xml(self) -> ET.Element:
-        testcase = ET.Element("testcase", self.attrs, time="%.3f" % self.duration)
+        testcase = ET.Element("testcase", self.attrs, time=f"{self.duration:.3f}")
         properties = self.make_properties_node()
         if properties is not None:
             testcase.append(properties)
         testcase.extend(self.nodes)
         return testcase
 
-    def _add_simple(self, tag: str, message: str, data: Optional[str] = None) -> None:
+    def _add_simple(self, tag: str, message: str, data: str | None = None) -> None:
         node = ET.Element(tag, message=message)
         node.text = bin_xml_escape(data)
         self.append(node)
@@ -199,7 +198,7 @@ def append_failure(self, report: TestReport) -> None:
             self._add_simple("skipped", "xfail-marked test passes unexpectedly")
         else:
             assert report.longrepr is not None
-            reprcrash: Optional[ReprFileLocation] = getattr(
+            reprcrash: ReprFileLocation | None = getattr(
                 report.longrepr, "reprcrash", None
             )
             if reprcrash is not None:
@@ -219,9 +218,7 @@ def append_collect_skipped(self, report: TestReport) -> None:
 
     def append_error(self, report: TestReport) -> None:
         assert report.longrepr is not None
-        reprcrash: Optional[ReprFileLocation] = getattr(
-            report.longrepr, "reprcrash", None
-        )
+        reprcrash: ReprFileLocation | None = getattr(report.longrepr, "reprcrash", None)
         if reprcrash is not None:
             reason = reprcrash.message
         else:
@@ -231,7 +228,7 @@ def append_error(self, report: TestReport) -> None:
             msg = f'failed on teardown with "{reason}"'
         else:
             msg = f'failed on setup with "{reason}"'
-        self._add_simple("error", msg, str(report.longrepr))
+        self._add_simple("error", bin_xml_escape(msg), str(report.longrepr))
 
     def append_skipped(self, report: TestReport) -> None:
         if hasattr(report, "wasxfail"):
@@ -248,7 +245,9 @@ def append_skipped(self, report: TestReport) -> None:
                 skipreason = skipreason[9:]
             details = f"{filename}:{lineno}: {skipreason}"
 
-            skipped = ET.Element("skipped", type="pytest.skip", message=skipreason)
+            skipped = ET.Element(
+                "skipped", type="pytest.skip", message=bin_xml_escape(skipreason)
+            )
             skipped.text = bin_xml_escape(details)
             self.append(skipped)
             self.write_captured_output(report)
@@ -256,9 +255,9 @@ def append_skipped(self, report: TestReport) -> None:
     def finalize(self) -> None:
         data = self.to_xml()
         self.__dict__.clear()
-        # Type ignored becuase mypy doesn't like overriding a method.
+        # Type ignored because mypy doesn't like overriding a method.
         # Also the return value doesn't match...
-        self.to_xml = lambda: data  # type: ignore[assignment]
+        self.to_xml = lambda: data  # type: ignore[method-assign]
 
 
 def _warn_incompatibility_with_xunit2(
@@ -271,9 +270,7 @@ def _warn_incompatibility_with_xunit2(
     if xml is not None and xml.family not in ("xunit1", "legacy"):
         request.node.warn(
             PytestWarning(
-                "{fixture_name} is incompatible with junit_family '{family}' (use 'legacy' or 'xunit1')".format(
-                    fixture_name=fixture_name, family=xml.family
-                )
+                f"{fixture_name} is incompatible with junit_family '{xml.family}' (use 'legacy' or 'xunit1')"
             )
         )
 
@@ -354,7 +351,10 @@ def test_foo(record_testsuite_property):
             record_testsuite_property("ARCH", "PPC")
             record_testsuite_property("STORAGE_TYPE", "CEPH")
 
-    ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped.
+    :param name:
+        The property name.
+    :param value:
+        The property value. Will be converted to a string.
 
     .. warning::
 
@@ -362,17 +362,16 @@ def test_foo(record_testsuite_property):
         `pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See
         :issue:`7767` for details.
     """
-
     __tracebackhide__ = True
 
     def record_func(name: str, value: object) -> None:
-        """No-op function in case --junitxml was not passed in the command-line."""
+        """No-op function in case --junit-xml was not passed in the command-line."""
         __tracebackhide__ = True
         _check_record_param_type("name", name)
 
     xml = request.config.stash.get(xml_key, None)
     if xml is not None:
-        record_func = xml.add_global_property  # noqa
+        record_func = xml.add_global_property
     return record_func
 
 
@@ -386,7 +385,7 @@ def pytest_addoption(parser: Parser) -> None:
         metavar="path",
         type=functools.partial(filename_arg, optname="--junitxml"),
         default=None,
-        help="create junit-xml style report file at given path.",
+        help="Create junit-xml style report file at given path",
     )
     group.addoption(
         "--junitprefix",
@@ -394,7 +393,7 @@ def pytest_addoption(parser: Parser) -> None:
         action="store",
         metavar="str",
         default=None,
-        help="prepend prefix to classnames in junit-xml output",
+        help="Prepend prefix to classnames in junit-xml output",
     )
     parser.addini(
         "junit_suite_name", "Test suite name for JUnit report", default="pytest"
@@ -447,7 +446,7 @@ def pytest_unconfigure(config: Config) -> None:
         config.pluginmanager.unregister(xml)
 
 
-def mangle_test_address(address: str) -> List[str]:
+def mangle_test_address(address: str) -> list[str]:
     path, possible_open_bracket, params = address.partition("[")
     names = path.split("::")
     # Convert file path to dotted path.
@@ -462,7 +461,7 @@ class LogXML:
     def __init__(
         self,
         logfile,
-        prefix: Optional[str],
+        prefix: str | None,
         suite_name: str = "pytest",
         logging: str = "no",
         report_duration: str = "total",
@@ -477,17 +476,15 @@ def __init__(
         self.log_passing_tests = log_passing_tests
         self.report_duration = report_duration
         self.family = family
-        self.stats: Dict[str, int] = dict.fromkeys(
+        self.stats: dict[str, int] = dict.fromkeys(
             ["error", "passed", "failure", "skipped"], 0
         )
-        self.node_reporters: Dict[
-            Tuple[Union[str, TestReport], object], _NodeReporter
-        ] = {}
-        self.node_reporters_ordered: List[_NodeReporter] = []
-        self.global_properties: List[Tuple[str, str]] = []
+        self.node_reporters: dict[tuple[str | TestReport, object], _NodeReporter] = {}
+        self.node_reporters_ordered: list[_NodeReporter] = []
+        self.global_properties: list[tuple[str, str]] = []
 
         # List of reports that failed on call but teardown is pending.
-        self.open_reports: List[TestReport] = []
+        self.open_reports: list[TestReport] = []
         self.cnt_double_fail_tests = 0
 
         # Replaces convenience family with real family.
@@ -499,11 +496,15 @@ def finalize(self, report: TestReport) -> None:
         # Local hack to handle xdist report order.
         workernode = getattr(report, "node", None)
         reporter = self.node_reporters.pop((nodeid, workernode))
+
+        for propname, propvalue in report.user_properties:
+            reporter.add_property(propname, str(propvalue))
+
         if reporter is not None:
             reporter.finalize()
 
-    def node_reporter(self, report: Union[TestReport, str]) -> _NodeReporter:
-        nodeid: Union[str, TestReport] = getattr(report, "nodeid", report)
+    def node_reporter(self, report: TestReport | str) -> _NodeReporter:
+        nodeid: str | TestReport = getattr(report, "nodeid", report)
         # Local hack to handle xdist report order.
         workernode = getattr(report, "node", None)
 
@@ -596,9 +597,6 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
             reporter = self._opentestcase(report)
             reporter.write_captured_output(report)
 
-            for propname, propvalue in report.user_properties:
-                reporter.add_property(propname, str(propvalue))
-
             self.finalize(report)
             report_wid = getattr(report, "worker_id", None)
             report_ii = getattr(report, "item_index", None)
@@ -620,7 +618,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
     def update_testcase_duration(self, report: TestReport) -> None:
         """Accumulate total duration for nodeid from given report and update
         the Junit.testcase with the new total if already created."""
-        if self.report_duration == "total" or report.when == self.report_duration:
+        if self.report_duration in {"total", report.when}:
             reporter = self.node_reporter(report)
             reporter.duration += getattr(report, "duration", 0.0)
 
@@ -642,8 +640,8 @@ def pytest_sessionstart(self) -> None:
 
     def pytest_sessionfinish(self) -> None:
         dirname = os.path.dirname(os.path.abspath(self.logfile))
-        if not os.path.isdir(dirname):
-            os.makedirs(dirname)
+        # exist_ok avoids filesystem race conditions between checking path existence and requesting creation
+        os.makedirs(dirname, exist_ok=True)
 
         with open(self.logfile, "w", encoding="utf-8") as logfile:
             suite_stop_time = timing.time()
@@ -665,8 +663,10 @@ def pytest_sessionfinish(self) -> None:
                 failures=str(self.stats["failure"]),
                 skipped=str(self.stats["skipped"]),
                 tests=str(numtests),
-                time="%.3f" % suite_time_delta,
-                timestamp=datetime.fromtimestamp(self.suite_start_time).isoformat(),
+                time=f"{suite_time_delta:.3f}",
+                timestamp=datetime.fromtimestamp(self.suite_start_time, timezone.utc)
+                .astimezone()
+                .isoformat(),
                 hostname=platform.node(),
             )
             global_properties = self._get_global_properties_node()
@@ -686,7 +686,7 @@ def add_global_property(self, name: str, value: object) -> None:
         _check_record_param_type("name", name)
         self.global_properties.append((name, bin_xml_escape(value)))
 
-    def _get_global_properties_node(self) -> Optional[ET.Element]:
+    def _get_global_properties_node(self) -> ET.Element | None:
         """Return a Junit node containing custom properties, if any."""
         if self.global_properties:
             properties = ET.Element("properties")
diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py
index 743c06d55c4..59e8ef6e742 100644
--- a/src/_pytest/legacypath.py
+++ b/src/_pytest/legacypath.py
@@ -1,26 +1,40 @@
+# mypy: allow-untyped-defs
 """Add backward compatibility support for the legacy py path type."""
+
+from __future__ import annotations
+
+import dataclasses
+from pathlib import Path
 import shlex
 import subprocess
-from pathlib import Path
-from typing import List
-from typing import Optional
+from typing import Final
+from typing import final
 from typing import TYPE_CHECKING
-from typing import Union
 
-import attr
 from iniconfig import SectionWrapper
 
-import pytest
-from _pytest.compat import final
+from _pytest.cacheprovider import Cache
 from _pytest.compat import LEGACY_PATH
 from _pytest.compat import legacy_path
+from _pytest.config import Config
+from _pytest.config import hookimpl
+from _pytest.config import PytestPluginManager
 from _pytest.deprecated import check_ispytest
+from _pytest.fixtures import fixture
+from _pytest.fixtures import FixtureRequest
+from _pytest.main import Session
+from _pytest.monkeypatch import MonkeyPatch
+from _pytest.nodes import Collector
+from _pytest.nodes import Item
 from _pytest.nodes import Node
+from _pytest.pytester import HookRecorder
+from _pytest.pytester import Pytester
+from _pytest.pytester import RunResult
 from _pytest.terminal import TerminalReporter
+from _pytest.tmpdir import TempPathFactory
 
-if TYPE_CHECKING:
-    from typing_extensions import Final
 
+if TYPE_CHECKING:
     import pexpect
 
 
@@ -35,10 +49,10 @@ class Testdir:
 
     __test__ = False
 
-    CLOSE_STDIN: "Final" = pytest.Pytester.CLOSE_STDIN
-    TimeoutExpired: "Final" = pytest.Pytester.TimeoutExpired
+    CLOSE_STDIN: Final = Pytester.CLOSE_STDIN
+    TimeoutExpired: Final = Pytester.TimeoutExpired
 
-    def __init__(self, pytester: pytest.Pytester, *, _ispytest: bool = False) -> None:
+    def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
         check_ispytest(_ispytest)
         self._pytester = pytester
 
@@ -64,10 +78,10 @@ def plugins(self, plugins):
         self._pytester.plugins = plugins
 
     @property
-    def monkeypatch(self) -> pytest.MonkeyPatch:
+    def monkeypatch(self) -> MonkeyPatch:
         return self._pytester._monkeypatch
 
-    def make_hook_recorder(self, pluginmanager) -> pytest.HookRecorder:
+    def make_hook_recorder(self, pluginmanager) -> HookRecorder:
         """See :meth:`Pytester.make_hook_recorder`."""
         return self._pytester.make_hook_recorder(pluginmanager)
 
@@ -76,7 +90,6 @@ def chdir(self) -> None:
         return self._pytester.chdir()
 
     def finalize(self) -> None:
-        """See :meth:`Pytester._finalize`."""
         return self._pytester._finalize()
 
     def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH:
@@ -131,9 +144,7 @@ def copy_example(self, name=None) -> LEGACY_PATH:
         """See :meth:`Pytester.copy_example`."""
         return legacy_path(self._pytester.copy_example(name))
 
-    def getnode(
-        self, config: pytest.Config, arg
-    ) -> Optional[Union[pytest.Item, pytest.Collector]]:
+    def getnode(self, config: Config, arg) -> Item | Collector | None:
         """See :meth:`Pytester.getnode`."""
         return self._pytester.getnode(config, arg)
 
@@ -141,9 +152,7 @@ def getpathnode(self, path):
         """See :meth:`Pytester.getpathnode`."""
         return self._pytester.getpathnode(path)
 
-    def genitems(
-        self, colitems: List[Union[pytest.Item, pytest.Collector]]
-    ) -> List[pytest.Item]:
+    def genitems(self, colitems: list[Item | Collector]) -> list[Item]:
         """See :meth:`Pytester.genitems`."""
         return self._pytester.genitems(colitems)
 
@@ -165,19 +174,19 @@ def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
             *args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
         )
 
-    def runpytest_inprocess(self, *args, **kwargs) -> pytest.RunResult:
+    def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
         """See :meth:`Pytester.runpytest_inprocess`."""
         return self._pytester.runpytest_inprocess(*args, **kwargs)
 
-    def runpytest(self, *args, **kwargs) -> pytest.RunResult:
+    def runpytest(self, *args, **kwargs) -> RunResult:
         """See :meth:`Pytester.runpytest`."""
         return self._pytester.runpytest(*args, **kwargs)
 
-    def parseconfig(self, *args) -> pytest.Config:
+    def parseconfig(self, *args) -> Config:
         """See :meth:`Pytester.parseconfig`."""
         return self._pytester.parseconfig(*args)
 
-    def parseconfigure(self, *args) -> pytest.Config:
+    def parseconfigure(self, *args) -> Config:
         """See :meth:`Pytester.parseconfigure`."""
         return self._pytester.parseconfigure(*args)
 
@@ -195,9 +204,7 @@ def getmodulecol(self, source, configargs=(), withinit=False):
             source, configargs=configargs, withinit=withinit
         )
 
-    def collect_by_name(
-        self, modcol: pytest.Collector, name: str
-    ) -> Optional[Union[pytest.Item, pytest.Collector]]:
+    def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None:
         """See :meth:`Pytester.collect_by_name`."""
         return self._pytester.collect_by_name(modcol, name)
 
@@ -212,11 +219,11 @@ def popen(
         """See :meth:`Pytester.popen`."""
         return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
 
-    def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> pytest.RunResult:
+    def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
         """See :meth:`Pytester.run`."""
         return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
 
-    def runpython(self, script) -> pytest.RunResult:
+    def runpython(self, script) -> RunResult:
         """See :meth:`Pytester.runpython`."""
         return self._pytester.runpython(script)
 
@@ -224,17 +231,15 @@ def runpython_c(self, command):
         """See :meth:`Pytester.runpython_c`."""
         return self._pytester.runpython_c(command)
 
-    def runpytest_subprocess(self, *args, timeout=None) -> pytest.RunResult:
+    def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
         """See :meth:`Pytester.runpytest_subprocess`."""
         return self._pytester.runpytest_subprocess(*args, timeout=timeout)
 
-    def spawn_pytest(
-        self, string: str, expect_timeout: float = 10.0
-    ) -> "pexpect.spawn":
+    def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn:
         """See :meth:`Pytester.spawn_pytest`."""
         return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
 
-    def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
+    def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn:
         """See :meth:`Pytester.spawn`."""
         return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
 
@@ -245,13 +250,10 @@ def __str__(self) -> str:
         return str(self.tmpdir)
 
 
-pytest.Testdir = Testdir  # type: ignore[attr-defined]
-
-
 class LegacyTestdirPlugin:
     @staticmethod
-    @pytest.fixture
-    def testdir(pytester: pytest.Pytester) -> Testdir:
+    @fixture
+    def testdir(pytester: Pytester) -> Testdir:
         """
         Identical to :fixture:`pytester`, and provides an instance whose methods return
         legacy ``LEGACY_PATH`` objects instead when applicable.
@@ -262,59 +264,63 @@ def testdir(pytester: pytest.Pytester) -> Testdir:
 
 
 @final
-@attr.s(init=False, auto_attribs=True)
+@dataclasses.dataclass
 class TempdirFactory:
-    """Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH``
-    for :class:``TempPathFactory``."""
+    """Backward compatibility wrapper that implements ``py.path.local``
+    for :class:`TempPathFactory`.
+
+    .. note::
+        These days, it is preferred to use ``tmp_path_factory``.
 
-    _tmppath_factory: pytest.TempPathFactory
+        :ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`.
+
+    """
+
+    _tmppath_factory: TempPathFactory
 
     def __init__(
-        self, tmppath_factory: pytest.TempPathFactory, *, _ispytest: bool = False
+        self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False
     ) -> None:
         check_ispytest(_ispytest)
         self._tmppath_factory = tmppath_factory
 
     def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH:
-        """Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object."""
+        """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object."""
         return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve())
 
     def getbasetemp(self) -> LEGACY_PATH:
-        """Backward compat wrapper for ``_tmppath_factory.getbasetemp``."""
+        """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object."""
         return legacy_path(self._tmppath_factory.getbasetemp().resolve())
 
 
-pytest.TempdirFactory = TempdirFactory  # type: ignore[attr-defined]
-
-
 class LegacyTmpdirPlugin:
     @staticmethod
-    @pytest.fixture(scope="session")
-    def tmpdir_factory(request: pytest.FixtureRequest) -> TempdirFactory:
+    @fixture(scope="session")
+    def tmpdir_factory(request: FixtureRequest) -> TempdirFactory:
         """Return a :class:`pytest.TempdirFactory` instance for the test session."""
         # Set dynamically by pytest_configure().
         return request.config._tmpdirhandler  # type: ignore
 
     @staticmethod
-    @pytest.fixture
+    @fixture
     def tmpdir(tmp_path: Path) -> LEGACY_PATH:
-        """Return a temporary directory path object which is unique to each test
-        function invocation, created as a sub directory of the base temporary
-        directory.
+        """Return a temporary directory (as `legacy_path`_ object)
+        which is unique to each test function invocation.
+        The temporary directory is created as a subdirectory
+        of the base temporary directory, with configurable retention,
+        as discussed in :ref:`temporary directory location and retention`.
 
-        By default, a new base temporary directory is created each test session,
-        and old bases are removed after 3 sessions, to aid in debugging. If
-        ``--basetemp`` is used then it is cleared each session. See :ref:`base
-        temporary directory`.
+        .. note::
+            These days, it is preferred to use ``tmp_path``.
 
-        The returned object is a `legacy_path`_ object.
+            :ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`.
 
         .. _legacy_path: https://py.readthedocs.io/en/latest/path.html
         """
         return legacy_path(tmp_path)
 
 
-def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH:
+def Cache_makedir(self: Cache, name: str) -> LEGACY_PATH:
     """Return a directory path object with the given name.
 
     Same as :func:`mkdir`, but returns a legacy py path instance.
@@ -322,7 +328,7 @@ def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH:
     return legacy_path(self.mkdir(name))
 
 
-def FixtureRequest_fspath(self: pytest.FixtureRequest) -> LEGACY_PATH:
+def FixtureRequest_fspath(self: FixtureRequest) -> LEGACY_PATH:
     """(deprecated) The file system path of the test module which collected this test."""
     return legacy_path(self.path)
 
@@ -337,7 +343,7 @@ def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH:
     return legacy_path(self.startpath)
 
 
-def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH:
+def Config_invocation_dir(self: Config) -> LEGACY_PATH:
     """The directory from which pytest was invoked.
 
     Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
@@ -348,7 +354,7 @@ def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH:
     return legacy_path(str(self.invocation_params.dir))
 
 
-def Config_rootdir(self: pytest.Config) -> LEGACY_PATH:
+def Config_rootdir(self: Config) -> LEGACY_PATH:
     """The path to the :ref:`rootdir <rootdir>`.
 
     Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
@@ -358,7 +364,7 @@ def Config_rootdir(self: pytest.Config) -> LEGACY_PATH:
     return legacy_path(str(self.rootpath))
 
 
-def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]:
+def Config_inifile(self: Config) -> LEGACY_PATH | None:
     """The path to the :ref:`configfile <configfiles>`.
 
     Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
@@ -368,7 +374,7 @@ def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]:
     return legacy_path(str(self.inipath)) if self.inipath else None
 
 
-def Session_stardir(self: pytest.Session) -> LEGACY_PATH:
+def Session_startdir(self: Session) -> LEGACY_PATH:
     """The path from which pytest was invoked.
 
     Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
@@ -378,9 +384,7 @@ def Session_stardir(self: pytest.Session) -> LEGACY_PATH:
     return legacy_path(self.startpath)
 
 
-def Config__getini_unknown_type(
-    self, name: str, type: str, value: Union[str, List[str]]
-):
+def Config__getini_unknown_type(self, name: str, type: str, value: str | list[str]):
     if type == "pathlist":
         # TODO: This assert is probably not valid in all cases.
         assert self.inipath is not None
@@ -400,35 +404,17 @@ def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None:
     self.path = Path(value)
 
 
-@pytest.hookimpl
-def pytest_configure(config: pytest.Config) -> None:
-    mp = pytest.MonkeyPatch()
-    config.add_cleanup(mp.undo)
-
-    if config.pluginmanager.has_plugin("tmpdir"):
-        # Create TmpdirFactory and attach it to the config object.
-        #
-        # This is to comply with existing plugins which expect the handler to be
-        # available at pytest_configure time, but ideally should be moved entirely
-        # to the tmpdir_factory session fixture.
-        try:
-            tmp_path_factory = config._tmp_path_factory  # type: ignore[attr-defined]
-        except AttributeError:
-            # tmpdir plugin is blocked.
-            pass
-        else:
-            _tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True)
-            mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False)
-
-        config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir")
+@hookimpl(tryfirst=True)
+def pytest_load_initial_conftests(early_config: Config) -> None:
+    """Monkeypatch legacy path attributes in several classes, as early as possible."""
+    mp = MonkeyPatch()
+    early_config.add_cleanup(mp.undo)
 
     # Add Cache.makedir().
-    mp.setattr(pytest.Cache, "makedir", Cache_makedir, raising=False)
+    mp.setattr(Cache, "makedir", Cache_makedir, raising=False)
 
     # Add FixtureRequest.fspath property.
-    mp.setattr(
-        pytest.FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False
-    )
+    mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False)
 
     # Add TerminalReporter.startdir property.
     mp.setattr(
@@ -436,26 +422,45 @@ def pytest_configure(config: pytest.Config) -> None:
     )
 
     # Add Config.{invocation_dir,rootdir,inifile} properties.
-    mp.setattr(
-        pytest.Config, "invocation_dir", property(Config_invocation_dir), raising=False
-    )
-    mp.setattr(pytest.Config, "rootdir", property(Config_rootdir), raising=False)
-    mp.setattr(pytest.Config, "inifile", property(Config_inifile), raising=False)
+    mp.setattr(Config, "invocation_dir", property(Config_invocation_dir), raising=False)
+    mp.setattr(Config, "rootdir", property(Config_rootdir), raising=False)
+    mp.setattr(Config, "inifile", property(Config_inifile), raising=False)
 
     # Add Session.startdir property.
-    mp.setattr(pytest.Session, "startdir", property(Session_stardir), raising=False)
+    mp.setattr(Session, "startdir", property(Session_startdir), raising=False)
 
     # Add pathlist configuration type.
-    mp.setattr(pytest.Config, "_getini_unknown_type", Config__getini_unknown_type)
+    mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type)
 
     # Add Node.fspath property.
     mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False)
 
 
-@pytest.hookimpl
-def pytest_plugin_registered(
-    plugin: object, manager: pytest.PytestPluginManager
-) -> None:
+@hookimpl
+def pytest_configure(config: Config) -> None:
+    """Installs the LegacyTmpdirPlugin if the ``tmpdir`` plugin is also installed."""
+    if config.pluginmanager.has_plugin("tmpdir"):
+        mp = MonkeyPatch()
+        config.add_cleanup(mp.undo)
+        # Create TmpdirFactory and attach it to the config object.
+        #
+        # This is to comply with existing plugins which expect the handler to be
+        # available at pytest_configure time, but ideally should be moved entirely
+        # to the tmpdir_factory session fixture.
+        try:
+            tmp_path_factory = config._tmp_path_factory  # type: ignore[attr-defined]
+        except AttributeError:
+            # tmpdir plugin is blocked.
+            pass
+        else:
+            _tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True)
+            mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False)
+
+        config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir")
+
+
+@hookimpl
+def pytest_plugin_registered(plugin: object, manager: PytestPluginManager) -> None:
     # pytester is not loaded by default and is commonly loaded from a conftest,
     # so checking for it in `pytest_configure` is not enough.
     is_pytester = plugin is manager.get_plugin("pytester")
diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py
index 31ad8301076..ca5fbda6fcc 100644
--- a/src/_pytest/logging.py
+++ b/src/_pytest/logging.py
@@ -1,26 +1,33 @@
+# mypy: allow-untyped-defs
 """Access and control log capturing."""
-import logging
-import os
-import re
-import sys
+
+from __future__ import annotations
+
+from collections.abc import Generator
+from collections.abc import Mapping
+from collections.abc import Set as AbstractSet
 from contextlib import contextmanager
+from contextlib import nullcontext
+from datetime import datetime
+from datetime import timedelta
+from datetime import timezone
+import io
 from io import StringIO
+import logging
+from logging import LogRecord
+import os
 from pathlib import Path
-from typing import AbstractSet
-from typing import Dict
-from typing import Generator
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Tuple
+import re
+from types import TracebackType
+from typing import final
+from typing import Generic
+from typing import Literal
+from typing import TYPE_CHECKING
 from typing import TypeVar
-from typing import Union
 
 from _pytest import nodes
 from _pytest._io import TerminalWriter
 from _pytest.capture import CaptureManager
-from _pytest.compat import final
-from _pytest.compat import nullcontext
 from _pytest.config import _strtobool
 from _pytest.config import Config
 from _pytest.config import create_terminal_writer
@@ -35,18 +42,42 @@
 from _pytest.terminal import TerminalReporter
 
 
+if TYPE_CHECKING:
+    logging_StreamHandler = logging.StreamHandler[StringIO]
+else:
+    logging_StreamHandler = logging.StreamHandler
+
 DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s"
 DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S"
 _ANSI_ESCAPE_SEQ = re.compile(r"\x1b\[[\d;]+m")
 caplog_handler_key = StashKey["LogCaptureHandler"]()
-caplog_records_key = StashKey[Dict[str, List[logging.LogRecord]]]()
+caplog_records_key = StashKey[dict[str, list[logging.LogRecord]]]()
 
 
 def _remove_ansi_escape_sequences(text: str) -> str:
     return _ANSI_ESCAPE_SEQ.sub("", text)
 
 
-class ColoredLevelFormatter(logging.Formatter):
+class DatetimeFormatter(logging.Formatter):
+    """A logging formatter which formats record with
+    :func:`datetime.datetime.strftime` formatter instead of
+    :func:`time.strftime` in case of microseconds in format string.
+    """
+
+    def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str:
+        if datefmt and "%f" in datefmt:
+            ct = self.converter(record.created)
+            tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone)
+            # Construct `datetime.datetime` object from `struct_time`
+            # and msecs information from `record`
+            # Using int() instead of round() to avoid it exceeding 1_000_000 and causing a ValueError (#11861).
+            dt = datetime(*ct[0:6], microsecond=int(record.msecs * 1000), tzinfo=tz)
+            return dt.strftime(datefmt)
+        # Use `logging.Formatter` for non-microsecond formats
+        return super().formatTime(record, datefmt)
+
+
+class ColoredLevelFormatter(DatetimeFormatter):
     """A logging formatter which colorizes the %(levelname)..s part of the
     log format passed to __init__."""
 
@@ -65,7 +96,7 @@ def __init__(self, terminalwriter: TerminalWriter, *args, **kwargs) -> None:
         super().__init__(*args, **kwargs)
         self._terminalwriter = terminalwriter
         self._original_fmt = self._style._fmt
-        self._level_to_fmt_mapping: Dict[int, str] = {}
+        self._level_to_fmt_mapping: dict[int, str] = {}
 
         for level, color_opts in self.LOGLEVEL_COLOROPTS.items():
             self.add_color_level(level, *color_opts)
@@ -83,7 +114,6 @@ def add_color_level(self, level: int, *color_opts: str) -> None:
         .. warning::
             This is an experimental API.
         """
-
         assert self._fmt is not None
         levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
         if not levelname_fmt_match:
@@ -114,12 +144,12 @@ class PercentStyleMultiline(logging.PercentStyle):
     formats the message as if each line were logged separately.
     """
 
-    def __init__(self, fmt: str, auto_indent: Union[int, str, bool, None]) -> None:
+    def __init__(self, fmt: str, auto_indent: int | str | bool | None) -> None:
         super().__init__(fmt)
         self._auto_indent = self._get_auto_indent(auto_indent)
 
     @staticmethod
-    def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int:
+    def _get_auto_indent(auto_indent_option: int | str | bool | None) -> int:
         """Determine the current auto indentation setting.
 
         Specify auto indent behavior (on/off/fixed) by passing in
@@ -150,7 +180,6 @@ def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int:
             0 (auto-indent turned off) or
             >0 (explicitly set indentation position).
         """
-
         if auto_indent_option is None:
             return 0
         elif isinstance(auto_indent_option, bool):
@@ -177,7 +206,7 @@ def format(self, record: logging.LogRecord) -> str:
         if "\n" in record.message:
             if hasattr(record, "auto_indent"):
                 # Passed in from the "extra={}" kwarg on the call to logging.log().
-                auto_indent = self._get_auto_indent(record.auto_indent)  # type: ignore[attr-defined]
+                auto_indent = self._get_auto_indent(record.auto_indent)
             else:
                 auto_indent = self._auto_indent
 
@@ -212,7 +241,7 @@ def pytest_addoption(parser: Parser) -> None:
 
     def add_option_ini(option, dest, default=None, type=None, **kwargs):
         parser.addini(
-            dest, default=default, type=type, help="default value for " + option
+            dest, default=default, type=type, help="Default value for " + option
         )
         group.addoption(option, dest=dest, **kwargs)
 
@@ -222,8 +251,8 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs):
         default=None,
         metavar="LEVEL",
         help=(
-            "level of messages to catch/display.\n"
-            "Not set by default, so it depends on the root/parent log handler's"
+            "Level of messages to catch/display."
+            " Not set by default, so it depends on the root/parent log handler's"
             ' effective level, where it is "WARNING" by default.'
         ),
     )
@@ -231,58 +260,65 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs):
         "--log-format",
         dest="log_format",
         default=DEFAULT_LOG_FORMAT,
-        help="log format as used by the logging module.",
+        help="Log format used by the logging module",
     )
     add_option_ini(
         "--log-date-format",
         dest="log_date_format",
         default=DEFAULT_LOG_DATE_FORMAT,
-        help="log date format as used by the logging module.",
+        help="Log date format used by the logging module",
     )
     parser.addini(
         "log_cli",
         default=False,
         type="bool",
-        help='enable log display during test run (also known as "live logging").',
+        help='Enable log display during test run (also known as "live logging")',
     )
     add_option_ini(
-        "--log-cli-level", dest="log_cli_level", default=None, help="cli logging level."
+        "--log-cli-level", dest="log_cli_level", default=None, help="CLI logging level"
     )
     add_option_ini(
         "--log-cli-format",
         dest="log_cli_format",
         default=None,
-        help="log format as used by the logging module.",
+        help="Log format used by the logging module",
     )
     add_option_ini(
         "--log-cli-date-format",
         dest="log_cli_date_format",
         default=None,
-        help="log date format as used by the logging module.",
+        help="Log date format used by the logging module",
     )
     add_option_ini(
         "--log-file",
         dest="log_file",
         default=None,
-        help="path to a file when logging will be written to.",
+        help="Path to a file when logging will be written to",
+    )
+    add_option_ini(
+        "--log-file-mode",
+        dest="log_file_mode",
+        default="w",
+        choices=["w", "a"],
+        help="Log file open mode",
     )
     add_option_ini(
         "--log-file-level",
         dest="log_file_level",
         default=None,
-        help="log file logging level.",
+        help="Log file logging level",
     )
     add_option_ini(
         "--log-file-format",
         dest="log_file_format",
-        default=DEFAULT_LOG_FORMAT,
-        help="log format as used by the logging module.",
+        default=None,
+        help="Log format used by the logging module",
     )
     add_option_ini(
         "--log-file-date-format",
         dest="log_file_date_format",
-        default=DEFAULT_LOG_DATE_FORMAT,
-        help="log date format as used by the logging module.",
+        default=None,
+        help="Log date format used by the logging module",
     )
     add_option_ini(
         "--log-auto-indent",
@@ -290,22 +326,29 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs):
         default=None,
         help="Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.",
     )
+    group.addoption(
+        "--log-disable",
+        action="append",
+        default=[],
+        dest="logger_disable",
+        help="Disable a logger by name. Can be passed multiple times.",
+    )
 
 
 _HandlerType = TypeVar("_HandlerType", bound=logging.Handler)
 
 
 # Not using @contextmanager for performance reasons.
-class catching_logs:
+class catching_logs(Generic[_HandlerType]):
     """Context manager that prepares the whole logging machinery properly."""
 
     __slots__ = ("handler", "level", "orig_level")
 
-    def __init__(self, handler: _HandlerType, level: Optional[int] = None) -> None:
+    def __init__(self, handler: _HandlerType, level: int | None = None) -> None:
         self.handler = handler
         self.level = level
 
-    def __enter__(self):
+    def __enter__(self) -> _HandlerType:
         root_logger = logging.getLogger()
         if self.level is not None:
             self.handler.setLevel(self.level)
@@ -315,22 +358,25 @@ def __enter__(self):
             root_logger.setLevel(min(self.orig_level, self.level))
         return self.handler
 
-    def __exit__(self, type, value, traceback):
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
+    ) -> None:
         root_logger = logging.getLogger()
         if self.level is not None:
             root_logger.setLevel(self.orig_level)
         root_logger.removeHandler(self.handler)
 
 
-class LogCaptureHandler(logging.StreamHandler):
+class LogCaptureHandler(logging_StreamHandler):
     """A logging handler that stores log records and the log text."""
 
-    stream: StringIO
-
     def __init__(self) -> None:
         """Create a new log handler."""
         super().__init__(StringIO())
-        self.records: List[logging.LogRecord] = []
+        self.records: list[logging.LogRecord] = []
 
     def emit(self, record: logging.LogRecord) -> None:
         """Keep the log records in a list in addition to the log text."""
@@ -341,13 +387,17 @@ def reset(self) -> None:
         self.records = []
         self.stream = StringIO()
 
+    def clear(self) -> None:
+        self.records.clear()
+        self.stream = StringIO()
+
     def handleError(self, record: logging.LogRecord) -> None:
         if logging.raiseExceptions:
             # Fail the test if the log message is bad (emit failed).
             # The default behavior of logging is to print "Logging error"
             # to stderr with the call stack and some extra details.
             # pytest wants to make such mistakes visible during testing.
-            raise
+            raise  # noqa: PLE0704
 
 
 @final
@@ -357,14 +407,15 @@ class LogCaptureFixture:
     def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None:
         check_ispytest(_ispytest)
         self._item = item
-        self._initial_handler_level: Optional[int] = None
+        self._initial_handler_level: int | None = None
         # Dict of log name -> log level.
-        self._initial_logger_levels: Dict[Optional[str], int] = {}
+        self._initial_logger_levels: dict[str | None, int] = {}
+        self._initial_disabled_logging_level: int | None = None
 
     def _finalize(self) -> None:
         """Finalize the fixture.
 
-        This restores the log levels changed by :meth:`set_level`.
+        This restores the log levels and the disabled logging levels changed by :meth:`set_level`.
         """
         # Restore log levels.
         if self._initial_handler_level is not None:
@@ -372,23 +423,26 @@ def _finalize(self) -> None:
         for logger_name, level in self._initial_logger_levels.items():
             logger = logging.getLogger(logger_name)
             logger.setLevel(level)
+        # Disable logging at the original disabled logging level.
+        if self._initial_disabled_logging_level is not None:
+            logging.disable(self._initial_disabled_logging_level)
+            self._initial_disabled_logging_level = None
 
     @property
     def handler(self) -> LogCaptureHandler:
-        """Get the logging handler used by the fixture.
-
-        :rtype: LogCaptureHandler
-        """
+        """Get the logging handler used by the fixture."""
         return self._item.stash[caplog_handler_key]
 
-    def get_records(self, when: str) -> List[logging.LogRecord]:
+    def get_records(
+        self, when: Literal["setup", "call", "teardown"]
+    ) -> list[logging.LogRecord]:
         """Get the logging records for one of the possible test phases.
 
-        :param str when:
-            Which test phase to obtain the records from. Valid values are: "setup", "call" and "teardown".
+        :param when:
+            Which test phase to obtain the records from.
+            Valid values are: "setup", "call" and "teardown".
 
         :returns: The list of captured records at the given stage.
-        :rtype: List[logging.LogRecord]
 
         .. versionadded:: 3.4
         """
@@ -400,12 +454,12 @@ def text(self) -> str:
         return _remove_ansi_escape_sequences(self.handler.stream.getvalue())
 
     @property
-    def records(self) -> List[logging.LogRecord]:
+    def records(self) -> list[logging.LogRecord]:
         """The list of log records."""
         return self.handler.records
 
     @property
-    def record_tuples(self) -> List[Tuple[str, int, str]]:
+    def record_tuples(self) -> list[tuple[str, int, str]]:
         """A list of a stripped down version of log records intended
         for use in assertion comparison.
 
@@ -416,7 +470,7 @@ def record_tuples(self) -> List[Tuple[str, int, str]]:
         return [(r.name, r.levelno, r.getMessage()) for r in self.records]
 
     @property
-    def messages(self) -> List[str]:
+    def messages(self) -> list[str]:
         """A list of format-interpolated log messages.
 
         Unlike 'records', which contains the format string and parameters for
@@ -436,17 +490,55 @@ def messages(self) -> List[str]:
 
     def clear(self) -> None:
         """Reset the list of log records and the captured log text."""
-        self.handler.reset()
+        self.handler.clear()
+
+    def _force_enable_logging(
+        self, level: int | str, logger_obj: logging.Logger
+    ) -> int:
+        """Enable the desired logging level if the global level was disabled via ``logging.disabled``.
+
+        Only enables logging levels greater than or equal to the requested ``level``.
+
+        Does nothing if the desired ``level`` wasn't disabled.
+
+        :param level:
+            The logger level caplog should capture.
+            All logging is enabled if a non-standard logging level string is supplied.
+            Valid level strings are in :data:`logging._nameToLevel`.
+        :param logger_obj: The logger object to check.
+
+        :return: The original disabled logging level.
+        """
+        original_disable_level: int = logger_obj.manager.disable
+
+        if isinstance(level, str):
+            # Try to translate the level string to an int for `logging.disable()`
+            level = logging.getLevelName(level)
+
+        if not isinstance(level, int):
+            # The level provided was not valid, so just un-disable all logging.
+            logging.disable(logging.NOTSET)
+        elif not logger_obj.isEnabledFor(level):
+            # Each level is `10` away from other levels.
+            # https://docs.python.org/3/library/logging.html#logging-levels
+            disable_level = max(level - 10, logging.NOTSET)
+            logging.disable(disable_level)
+
+        return original_disable_level
+
+    def set_level(self, level: int | str, logger: str | None = None) -> None:
+        """Set the threshold level of a logger for the duration of a test.
 
-    def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None:
-        """Set the level of a logger for the duration of a test.
+        Logging messages which are less severe than this level will not be captured.
 
         .. versionchanged:: 3.4
             The levels of the loggers changed by this function will be
             restored to their initial values at the end of the test.
 
-        :param int level: The level.
-        :param str logger: The logger to update. If not given, the root logger.
+        Will enable the requested logging level if it was disabled via :func:`logging.disable`.
+
+        :param level: The level.
+        :param logger: The logger to update. If not given, the root logger.
         """
         logger_obj = logging.getLogger(logger)
         # Save the original log-level to restore it during teardown.
@@ -455,32 +547,53 @@ def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> Non
         if self._initial_handler_level is None:
             self._initial_handler_level = self.handler.level
         self.handler.setLevel(level)
+        initial_disabled_logging_level = self._force_enable_logging(level, logger_obj)
+        if self._initial_disabled_logging_level is None:
+            self._initial_disabled_logging_level = initial_disabled_logging_level
 
     @contextmanager
-    def at_level(
-        self, level: Union[int, str], logger: Optional[str] = None
-    ) -> Generator[None, None, None]:
+    def at_level(self, level: int | str, logger: str | None = None) -> Generator[None]:
         """Context manager that sets the level for capturing of logs. After
         the end of the 'with' statement the level is restored to its original
         value.
 
-        :param int level: The level.
-        :param str logger: The logger to update. If not given, the root logger.
+        Will enable the requested logging level if it was disabled via :func:`logging.disable`.
+
+        :param level: The level.
+        :param logger: The logger to update. If not given, the root logger.
         """
         logger_obj = logging.getLogger(logger)
         orig_level = logger_obj.level
         logger_obj.setLevel(level)
         handler_orig_level = self.handler.level
         self.handler.setLevel(level)
+        original_disable_level = self._force_enable_logging(level, logger_obj)
         try:
             yield
         finally:
             logger_obj.setLevel(orig_level)
             self.handler.setLevel(handler_orig_level)
+            logging.disable(original_disable_level)
+
+    @contextmanager
+    def filtering(self, filter_: logging.Filter) -> Generator[None]:
+        """Context manager that temporarily adds the given filter to the caplog's
+        :meth:`handler` for the 'with' statement block, and removes that filter at the
+        end of the block.
+
+        :param filter_: A custom :class:`logging.Filter` object.
+
+        .. versionadded:: 7.5
+        """
+        self.handler.addFilter(filter_)
+        try:
+            yield
+        finally:
+            self.handler.removeFilter(filter_)
 
 
 @fixture
-def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]:
+def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture]:
     """Access and control log capturing.
 
     Captured logs are available through the following properties/methods::
@@ -496,7 +609,7 @@ def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]:
     result._finalize()
 
 
-def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]:
+def get_log_level_for_setting(config: Config, *setting_names: str) -> int | None:
     for setting_name in setting_names:
         log_level = config.getoption(setting_name)
         if log_level is None:
@@ -513,9 +626,9 @@ def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[i
     except ValueError as e:
         # Python logging does not recognise this as a logging level
         raise UsageError(
-            "'{}' is not recognized as a logging level name for "
-            "'{}'. Please consider passing the "
-            "logging level num instead.".format(log_level, setting_name)
+            f"'{log_level}' is not recognized as a logging level name for "
+            f"'{setting_name}'. Please consider passing the "
+            "logging level num instead."
         ) from e
 
 
@@ -549,20 +662,25 @@ def __init__(self, config: Config) -> None:
         self.report_handler.setFormatter(self.formatter)
 
         # File logging.
-        self.log_file_level = get_log_level_for_setting(config, "log_file_level")
+        self.log_file_level = get_log_level_for_setting(
+            config, "log_file_level", "log_level"
+        )
         log_file = get_option_ini(config, "log_file") or os.devnull
         if log_file != os.devnull:
             directory = os.path.dirname(os.path.abspath(log_file))
             if not os.path.isdir(directory):
                 os.makedirs(directory)
 
-        self.log_file_handler = _FileHandler(log_file, mode="w", encoding="UTF-8")
+        self.log_file_mode = get_option_ini(config, "log_file_mode") or "w"
+        self.log_file_handler = _FileHandler(
+            log_file, mode=self.log_file_mode, encoding="UTF-8"
+        )
         log_file_format = get_option_ini(config, "log_file_format", "log_format")
         log_file_date_format = get_option_ini(
             config, "log_file_date_format", "log_date_format"
         )
 
-        log_file_formatter = logging.Formatter(
+        log_file_formatter = DatetimeFormatter(
             log_file_format, datefmt=log_file_date_format
         )
         self.log_file_handler.setFormatter(log_file_formatter)
@@ -573,11 +691,13 @@ def __init__(self, config: Config) -> None:
         )
         if self._log_cli_enabled():
             terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
+            # Guaranteed by `_log_cli_enabled()`.
+            assert terminal_reporter is not None
             capture_manager = config.pluginmanager.get_plugin("capturemanager")
             # if capturemanager plugin is disabled, live logging still works.
-            self.log_cli_handler: Union[
-                _LiveLoggingStreamHandler, _LiveLoggingNullHandler
-            ] = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
+            self.log_cli_handler: (
+                _LiveLoggingStreamHandler | _LiveLoggingNullHandler
+            ) = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
         else:
             self.log_cli_handler = _LiveLoggingNullHandler()
         log_cli_formatter = self._create_formatter(
@@ -586,6 +706,15 @@ def __init__(self, config: Config) -> None:
             get_option_ini(config, "log_auto_indent"),
         )
         self.log_cli_handler.setFormatter(log_cli_formatter)
+        self._disable_loggers(loggers_to_disable=config.option.logger_disable)
+
+    def _disable_loggers(self, loggers_to_disable: list[str]) -> None:
+        if not loggers_to_disable:
+            return
+
+        for name in loggers_to_disable:
+            logger = logging.getLogger(name)
+            logger.disabled = True
 
     def _create_formatter(self, log_format, log_date_format, auto_indent):
         # Color option doesn't exist if terminal plugin is disabled.
@@ -597,7 +726,7 @@ def _create_formatter(self, log_format, log_date_format, auto_indent):
                 create_terminal_writer(self._config), log_format, log_date_format
             )
         else:
-            formatter = logging.Formatter(log_format, log_date_format)
+            formatter = DatetimeFormatter(log_format, log_date_format)
 
         formatter._style = PercentStyleMultiline(
             formatter._style._fmt, auto_indent=auto_indent
@@ -621,22 +750,13 @@ def set_log_path(self, fname: str) -> None:
         if not fpath.parent.exists():
             fpath.parent.mkdir(exist_ok=True, parents=True)
 
-        stream = fpath.open(mode="w", encoding="UTF-8")
-        if sys.version_info >= (3, 7):
-            old_stream = self.log_file_handler.setStream(stream)
-        else:
-            old_stream = self.log_file_handler.stream
-            self.log_file_handler.acquire()
-            try:
-                self.log_file_handler.flush()
-                self.log_file_handler.stream = stream
-            finally:
-                self.log_file_handler.release()
+        # https://github.com/python/mypy/issues/11193
+        stream: io.TextIOWrapper = fpath.open(mode=self.log_file_mode, encoding="UTF-8")  # type: ignore[assignment]
+        old_stream = self.log_file_handler.setStream(stream)
         if old_stream:
-            # https://github.com/python/typeshed/pull/5663
-            old_stream.close()  # type:ignore[attr-defined]
+            old_stream.close()
 
-    def _log_cli_enabled(self):
+    def _log_cli_enabled(self) -> bool:
         """Return whether live logging is enabled."""
         enabled = self._config.getoption(
             "--log-cli-level"
@@ -651,35 +771,34 @@ def _log_cli_enabled(self):
 
         return True
 
-    @hookimpl(hookwrapper=True, tryfirst=True)
-    def pytest_sessionstart(self) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True, tryfirst=True)
+    def pytest_sessionstart(self) -> Generator[None]:
         self.log_cli_handler.set_when("sessionstart")
 
         with catching_logs(self.log_cli_handler, level=self.log_cli_level):
             with catching_logs(self.log_file_handler, level=self.log_file_level):
-                yield
+                return (yield)
 
-    @hookimpl(hookwrapper=True, tryfirst=True)
-    def pytest_collection(self) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True, tryfirst=True)
+    def pytest_collection(self) -> Generator[None]:
         self.log_cli_handler.set_when("collection")
 
         with catching_logs(self.log_cli_handler, level=self.log_cli_level):
             with catching_logs(self.log_file_handler, level=self.log_file_level):
-                yield
+                return (yield)
 
-    @hookimpl(hookwrapper=True)
-    def pytest_runtestloop(self, session: Session) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_runtestloop(self, session: Session) -> Generator[None, object, object]:
         if session.config.option.collectonly:
-            yield
-            return
+            return (yield)
 
-        if self._log_cli_enabled() and self._config.getoption("verbose") < 1:
+        if self._log_cli_enabled() and self._config.get_verbosity() < 1:
             # The verbose flag is needed to avoid messy test progress output.
             self._config.option.verbose = 1
 
         with catching_logs(self.log_cli_handler, level=self.log_cli_level):
             with catching_logs(self.log_file_handler, level=self.log_file_level):
-                yield  # Run all the tests.
+                return (yield)  # Run all the tests.
 
     @hookimpl
     def pytest_runtest_logstart(self) -> None:
@@ -690,58 +809,64 @@ def pytest_runtest_logstart(self) -> None:
     def pytest_runtest_logreport(self) -> None:
         self.log_cli_handler.set_when("logreport")
 
-    def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, None]:
+    def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None]:
         """Implement the internals of the pytest_runtest_xxx() hooks."""
-        with catching_logs(
-            self.caplog_handler,
-            level=self.log_level,
-        ) as caplog_handler, catching_logs(
-            self.report_handler,
-            level=self.log_level,
-        ) as report_handler:
+        with (
+            catching_logs(
+                self.caplog_handler,
+                level=self.log_level,
+            ) as caplog_handler,
+            catching_logs(
+                self.report_handler,
+                level=self.log_level,
+            ) as report_handler,
+        ):
             caplog_handler.reset()
             report_handler.reset()
             item.stash[caplog_records_key][when] = caplog_handler.records
             item.stash[caplog_handler_key] = caplog_handler
 
-            yield
-
-            log = report_handler.stream.getvalue().strip()
-            item.add_report_section(when, "log", log)
+            try:
+                yield
+            finally:
+                log = report_handler.stream.getvalue().strip()
+                item.add_report_section(when, "log", log)
 
-    @hookimpl(hookwrapper=True)
-    def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None]:
         self.log_cli_handler.set_when("setup")
 
-        empty: Dict[str, List[logging.LogRecord]] = {}
+        empty: dict[str, list[logging.LogRecord]] = {}
         item.stash[caplog_records_key] = empty
         yield from self._runtest_for(item, "setup")
 
-    @hookimpl(hookwrapper=True)
-    def pytest_runtest_call(self, item: nodes.Item) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_runtest_call(self, item: nodes.Item) -> Generator[None]:
         self.log_cli_handler.set_when("call")
 
         yield from self._runtest_for(item, "call")
 
-    @hookimpl(hookwrapper=True)
-    def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_runtest_teardown(self, item: nodes.Item) -> Generator[None]:
         self.log_cli_handler.set_when("teardown")
 
-        yield from self._runtest_for(item, "teardown")
-        del item.stash[caplog_records_key]
-        del item.stash[caplog_handler_key]
+        try:
+            yield from self._runtest_for(item, "teardown")
+        finally:
+            del item.stash[caplog_records_key]
+            del item.stash[caplog_handler_key]
 
     @hookimpl
     def pytest_runtest_logfinish(self) -> None:
         self.log_cli_handler.set_when("finish")
 
-    @hookimpl(hookwrapper=True, tryfirst=True)
-    def pytest_sessionfinish(self) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True, tryfirst=True)
+    def pytest_sessionfinish(self) -> Generator[None]:
         self.log_cli_handler.set_when("sessionfinish")
 
         with catching_logs(self.log_cli_handler, level=self.log_cli_level):
             with catching_logs(self.log_file_handler, level=self.log_file_level):
-                yield
+                return (yield)
 
     @hookimpl
     def pytest_unconfigure(self) -> None:
@@ -758,7 +883,7 @@ def handleError(self, record: logging.LogRecord) -> None:
         pass
 
 
-class _LiveLoggingStreamHandler(logging.StreamHandler):
+class _LiveLoggingStreamHandler(logging_StreamHandler):
     """A logging StreamHandler used by the live logging feature: it will
     write a newline before the first log message in each test.
 
@@ -774,7 +899,7 @@ class _LiveLoggingStreamHandler(logging.StreamHandler):
     def __init__(
         self,
         terminal_reporter: TerminalReporter,
-        capture_manager: Optional[CaptureManager],
+        capture_manager: CaptureManager | None,
     ) -> None:
         super().__init__(stream=terminal_reporter)  # type: ignore[arg-type]
         self.capture_manager = capture_manager
@@ -786,7 +911,7 @@ def reset(self) -> None:
         """Reset the handler; should be called before the start of each test."""
         self._first_record_emitted = False
 
-    def set_when(self, when: Optional[str]) -> None:
+    def set_when(self, when: str | None) -> None:
         """Prepare for the given test phase (setup/call/teardown)."""
         self._when = when
         self._section_name_shown = False
diff --git a/src/_pytest/main.py b/src/_pytest/main.py
index 57407fe5495..dac084b553a 100644
--- a/src/_pytest/main.py
+++ b/src/_pytest/main.py
@@ -1,30 +1,31 @@
 """Core implementation of the testing process: init, session, runtest loop."""
+
+from __future__ import annotations
+
 import argparse
+from collections.abc import Callable
+from collections.abc import Iterable
+from collections.abc import Iterator
+from collections.abc import Sequence
+from collections.abc import Set as AbstractSet
+import dataclasses
 import fnmatch
 import functools
 import importlib
+import importlib.util
 import os
-import sys
 from pathlib import Path
-from typing import Callable
-from typing import Dict
-from typing import FrozenSet
-from typing import Iterator
-from typing import List
-from typing import Optional
+import sys
+from typing import final
+from typing import Literal
 from typing import overload
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
 from typing import TYPE_CHECKING
-from typing import Union
+import warnings
 
-import attr
+import pluggy
 
-import _pytest._code
 from _pytest import nodes
-from _pytest.compat import final
+import _pytest._code
 from _pytest.config import Config
 from _pytest.config import directory_arg
 from _pytest.config import ExitCode
@@ -32,115 +33,76 @@
 from _pytest.config import PytestPluginManager
 from _pytest.config import UsageError
 from _pytest.config.argparsing import Parser
-from _pytest.fixtures import FixtureManager
+from _pytest.config.compat import PathAwareHookProxy
 from _pytest.outcomes import exit
 from _pytest.pathlib import absolutepath
 from _pytest.pathlib import bestrelpath
 from _pytest.pathlib import fnmatch_ex
-from _pytest.pathlib import visit
+from _pytest.pathlib import safe_exists
+from _pytest.pathlib import scandir
 from _pytest.reports import CollectReport
 from _pytest.reports import TestReport
 from _pytest.runner import collect_one_node
 from _pytest.runner import SetupState
+from _pytest.warning_types import PytestWarning
 
 
 if TYPE_CHECKING:
-    from typing_extensions import Literal
+    from typing_extensions import Self
+
+    from _pytest.fixtures import FixtureManager
 
 
 def pytest_addoption(parser: Parser) -> None:
-    parser.addini(
-        "norecursedirs",
-        "directory patterns to avoid for recursion",
-        type="args",
-        default=[
-            "*.egg",
-            ".*",
-            "_darcs",
-            "build",
-            "CVS",
-            "dist",
-            "node_modules",
-            "venv",
-            "{arch}",
-        ],
-    )
-    parser.addini(
-        "testpaths",
-        "directories to search for tests when no files or directories are given in the "
-        "command line.",
-        type="args",
-        default=[],
-    )
-    group = parser.getgroup("general", "running and selection options")
-    group._addoption(
+    group = parser.getgroup("general", "Running and selection options")
+    group._addoption(  # private to use reserved lower-case short option
         "-x",
         "--exitfirst",
         action="store_const",
         dest="maxfail",
         const=1,
-        help="exit instantly on first error or failed test.",
+        help="Exit instantly on first error or failed test",
     )
-    group = parser.getgroup("pytest-warnings")
     group.addoption(
-        "-W",
-        "--pythonwarnings",
-        action="append",
-        help="set which warnings to report, see -W option of python itself.",
-    )
-    parser.addini(
-        "filterwarnings",
-        type="linelist",
-        help="Each line specifies a pattern for "
-        "warnings.filterwarnings. "
-        "Processed after -W/--pythonwarnings.",
-    )
-    group._addoption(
         "--maxfail",
         metavar="num",
         action="store",
         type=int,
         dest="maxfail",
         default=0,
-        help="exit after first num failures or errors.",
+        help="Exit after first num failures or errors",
     )
-    group._addoption(
+    group.addoption(
         "--strict-config",
         action="store_true",
-        help="any warnings encountered while parsing the `pytest` section of the configuration file raise errors.",
+        help="Any warnings encountered while parsing the `pytest` section of the "
+        "configuration file raise errors",
     )
-    group._addoption(
+    group.addoption(
         "--strict-markers",
         action="store_true",
-        help="markers not registered in the `markers` section of the configuration file raise errors.",
+        help="Markers not registered in the `markers` section of the configuration "
+        "file raise errors",
     )
-    group._addoption(
+    group.addoption(
         "--strict",
         action="store_true",
-        help="(deprecated) alias to --strict-markers.",
-    )
-    group._addoption(
-        "-c",
-        metavar="file",
-        type=str,
-        dest="inifilename",
-        help="load configuration from `file` instead of trying to locate one of the implicit "
-        "configuration files.",
+        help="(Deprecated) alias to --strict-markers",
     )
-    group._addoption(
-        "--continue-on-collection-errors",
-        action="store_true",
-        default=False,
-        dest="continue_on_collection_errors",
-        help="Force test execution even if collection errors occur.",
+
+    group = parser.getgroup("pytest-warnings")
+    group.addoption(
+        "-W",
+        "--pythonwarnings",
+        action="append",
+        help="Set which warnings to report, see -W option of Python itself",
     )
-    group._addoption(
-        "--rootdir",
-        action="store",
-        dest="rootdir",
-        help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
-        "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
-        "'$HOME/root_dir'.",
+    parser.addini(
+        "filterwarnings",
+        type="linelist",
+        help="Each line specifies a pattern for "
+        "warnings.filterwarnings. "
+        "Processed after -W/--pythonwarnings.",
     )
 
     group = parser.getgroup("collect", "collection")
@@ -149,30 +111,30 @@ def pytest_addoption(parser: Parser) -> None:
         "--collect-only",
         "--co",
         action="store_true",
-        help="only collect tests, don't execute them.",
+        help="Only collect tests, don't execute them",
     )
     group.addoption(
         "--pyargs",
         action="store_true",
-        help="try to interpret all arguments as python packages.",
+        help="Try to interpret all arguments as Python packages",
     )
     group.addoption(
         "--ignore",
         action="append",
         metavar="path",
-        help="ignore path during collection (multi-allowed).",
+        help="Ignore path during collection (multi-allowed)",
     )
     group.addoption(
         "--ignore-glob",
         action="append",
         metavar="path",
-        help="ignore path pattern during collection (multi-allowed).",
+        help="Ignore path pattern during collection (multi-allowed)",
     )
     group.addoption(
         "--deselect",
         action="append",
         metavar="nodeid_prefix",
-        help="deselect item (via node id prefix) during collection (multi-allowed).",
+        help="Deselect item (via node id prefix) during collection (multi-allowed)",
     )
     group.addoption(
         "--confcutdir",
@@ -180,14 +142,14 @@ def pytest_addoption(parser: Parser) -> None:
         default=None,
         metavar="dir",
         type=functools.partial(directory_arg, optname="--confcutdir"),
-        help="only load conftest.py's relative to specified dir.",
+        help="Only load conftest.py's relative to specified dir",
     )
     group.addoption(
         "--noconftest",
         action="store_true",
         dest="noconftest",
         default=False,
-        help="Don't load any conftest.py files.",
+        help="Don't load any conftest.py files",
     )
     group.addoption(
         "--keepduplicates",
@@ -195,7 +157,7 @@ def pytest_addoption(parser: Parser) -> None:
         action="store_true",
         dest="keepduplicates",
         default=False,
-        help="Keep duplicate tests.",
+        help="Keep duplicate tests",
     )
     group.addoption(
         "--collect-in-virtualenv",
@@ -204,16 +166,75 @@ def pytest_addoption(parser: Parser) -> None:
         default=False,
         help="Don't ignore tests in a local virtualenv directory",
     )
+    group.addoption(
+        "--continue-on-collection-errors",
+        action="store_true",
+        default=False,
+        dest="continue_on_collection_errors",
+        help="Force test execution even if collection errors occur",
+    )
     group.addoption(
         "--import-mode",
         default="prepend",
         choices=["prepend", "append", "importlib"],
         dest="importmode",
-        help="prepend/append to sys.path when importing test modules and conftest files, "
-        "default is to prepend.",
+        help="Prepend/append to sys.path when importing test modules and conftest "
+        "files. Default: prepend.",
+    )
+    parser.addini(
+        "norecursedirs",
+        "Directory patterns to avoid for recursion",
+        type="args",
+        default=[
+            "*.egg",
+            ".*",
+            "_darcs",
+            "build",
+            "CVS",
+            "dist",
+            "node_modules",
+            "venv",
+            "{arch}",
+        ],
+    )
+    parser.addini(
+        "testpaths",
+        "Directories to search for tests when no files or directories are given on the "
+        "command line",
+        type="args",
+        default=[],
+    )
+    parser.addini(
+        "collect_imported_tests",
+        "Whether to collect tests in imported modules outside `testpaths`",
+        type="bool",
+        default=True,
+    )
+    parser.addini(
+        "consider_namespace_packages",
+        type="bool",
+        default=False,
+        help="Consider namespace packages when resolving module names during import",
     )
 
     group = parser.getgroup("debugconfig", "test session debugging and configuration")
+    group._addoption(  # private to use reserved lower-case short option
+        "-c",
+        "--config-file",
+        metavar="FILE",
+        type=str,
+        dest="inifilename",
+        help="Load configuration from `FILE` instead of trying to locate one of the "
+        "implicit configuration files.",
+    )
+    group.addoption(
+        "--rootdir",
+        action="store",
+        dest="rootdir",
+        help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
+        "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
+        "'$HOME/root_dir'.",
+    )
     group.addoption(
         "--basetemp",
         dest="basetemp",
@@ -221,8 +242,8 @@ def pytest_addoption(parser: Parser) -> None:
         type=validate_basetemp,
         metavar="dir",
         help=(
-            "base temporary directory for this test run."
-            "(warning: this directory is removed if it exists)"
+            "Base temporary directory for this test run. "
+            "(Warning: this directory is removed if it exists.)"
         ),
     )
 
@@ -253,8 +274,8 @@ def is_ancestor(base: Path, query: Path) -> bool:
 
 
 def wrap_session(
-    config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
-) -> Union[int, ExitCode]:
+    config: Config, doit: Callable[[Config, Session], int | ExitCode | None]
+) -> int | ExitCode:
     """Skeleton command line program."""
     session = Session.from_config(config)
     session.exitstatus = ExitCode.OK
@@ -273,7 +294,7 @@ def wrap_session(
             session.exitstatus = ExitCode.TESTS_FAILED
         except (KeyboardInterrupt, exit.Exception):
             excinfo = _pytest._code.ExceptionInfo.from_current()
-            exitstatus: Union[int, ExitCode] = ExitCode.INTERRUPTED
+            exitstatus: int | ExitCode = ExitCode.INTERRUPTED
             if isinstance(excinfo.value, exit.Exception):
                 if excinfo.value.returncode is not None:
                     exitstatus = excinfo.value.returncode
@@ -311,11 +332,11 @@ def wrap_session(
     return session.exitstatus
 
 
-def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]:
+def pytest_cmdline_main(config: Config) -> int | ExitCode:
     return wrap_session(config, _main)
 
 
-def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
+def _main(config: Config, session: Session) -> int | ExitCode | None:
     """Default command line protocol for initialization, session,
     running tests and reporting."""
     config.hook.pytest_collection(session=session)
@@ -328,15 +349,14 @@ def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
     return None
 
 
-def pytest_collection(session: "Session") -> None:
+def pytest_collection(session: Session) -> None:
     session.perform_collect()
 
 
-def pytest_runtestloop(session: "Session") -> bool:
+def pytest_runtestloop(session: Session) -> bool:
     if session.testsfailed and not session.config.option.continue_on_collection_errors:
         raise session.Interrupted(
-            "%d error%s during collection"
-            % (session.testsfailed, "s" if session.testsfailed != 1 else "")
+            f"{session.testsfailed} error{'s' if session.testsfailed != 1 else ''} during collection"
         )
 
     if session.config.option.collectonly:
@@ -354,27 +374,31 @@ def pytest_runtestloop(session: "Session") -> bool:
 
 def _in_venv(path: Path) -> bool:
     """Attempt to detect if ``path`` is the root of a Virtual Environment by
-    checking for the existence of the appropriate activate script."""
-    bindir = path.joinpath("Scripts" if sys.platform.startswith("win") else "bin")
+    checking for the existence of the pyvenv.cfg file.
+
+    [https://peps.python.org/pep-0405/]
+
+    For regression protection we also check for conda environments that do not include pyenv.cfg yet --
+    https://github.com/conda/conda/issues/13337 is the conda issue tracking adding pyenv.cfg.
+
+    Checking for the `conda-meta/history` file per https://github.com/pytest-dev/pytest/issues/12652#issuecomment-2246336902.
+
+    """
     try:
-        if not bindir.is_dir():
-            return False
+        return (
+            path.joinpath("pyvenv.cfg").is_file()
+            or path.joinpath("conda-meta", "history").is_file()
+        )
     except OSError:
         return False
-    activates = (
-        "activate",
-        "activate.csh",
-        "activate.fish",
-        "Activate",
-        "Activate.bat",
-        "Activate.ps1",
-    )
-    return any(fname.name in activates for fname in bindir.iterdir())
 
 
-def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
+def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None:
+    if collection_path.name == "__pycache__":
+        return True
+
     ignore_paths = config._getconftest_pathlist(
-        "collect_ignore", path=collection_path.parent, rootpath=config.rootpath
+        "collect_ignore", path=collection_path.parent
     )
     ignore_paths = ignore_paths or []
     excludeopt = config.getoption("ignore")
@@ -385,7 +409,7 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
         return True
 
     ignore_globs = config._getconftest_pathlist(
-        "collect_ignore_glob", path=collection_path.parent, rootpath=config.rootpath
+        "collect_ignore_glob", path=collection_path.parent
     )
     ignore_globs = ignore_globs or []
     excludeglobopt = config.getoption("ignore_glob")
@@ -398,10 +422,22 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
     allow_in_venv = config.getoption("collect_in_virtualenv")
     if not allow_in_venv and _in_venv(collection_path):
         return True
+
+    if collection_path.is_dir():
+        norecursepatterns = config.getini("norecursedirs")
+        if any(fnmatch_ex(pat, collection_path) for pat in norecursepatterns):
+            return True
+
     return None
 
 
-def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None:
+def pytest_collect_directory(
+    path: Path, parent: nodes.Collector
+) -> nodes.Collector | None:
+    return Dir.from_parent(parent, path=path)
+
+
+def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None:
     deselect_prefixes = tuple(config.getoption("deselect") or [])
     if not deselect_prefixes:
         return
@@ -420,11 +456,15 @@ def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> No
 
 
 class FSHookProxy:
-    def __init__(self, pm: PytestPluginManager, remove_mods) -> None:
+    def __init__(
+        self,
+        pm: PytestPluginManager,
+        remove_mods: AbstractSet[object],
+    ) -> None:
         self.pm = pm
         self.remove_mods = remove_mods
 
-    def __getattr__(self, name: str):
+    def __getattr__(self, name: str) -> pluggy.HookCaller:
         x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
         self.__dict__[name] = x
         return x
@@ -440,8 +480,10 @@ class Failed(Exception):
     """Signals a stop as failed test run."""
 
 
-@attr.s(slots=True, auto_attribs=True)
-class _bestrelpath_cache(Dict[Path, str]):
+@dataclasses.dataclass
+class _bestrelpath_cache(dict[Path, str]):
+    __slots__ = ("path",)
+
     path: Path
 
     def __missing__(self, path: Path) -> str:
@@ -451,17 +493,75 @@ def __missing__(self, path: Path) -> str:
 
 
 @final
-class Session(nodes.FSCollector):
+class Dir(nodes.Directory):
+    """Collector of files in a file system directory.
+
+    .. versionadded:: 8.0
+
+    .. note::
+
+        Python directories with an `__init__.py` file are instead collected by
+        :class:`~pytest.Package` by default. Both are :class:`~pytest.Directory`
+        collectors.
+    """
+
+    @classmethod
+    def from_parent(  # type: ignore[override]
+        cls,
+        parent: nodes.Collector,
+        *,
+        path: Path,
+    ) -> Self:
+        """The public constructor.
+
+        :param parent: The parent collector of this Dir.
+        :param path: The directory's path.
+        :type path: pathlib.Path
+        """
+        return super().from_parent(parent=parent, path=path)
+
+    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
+        config = self.config
+        col: nodes.Collector | None
+        cols: Sequence[nodes.Collector]
+        ihook = self.ihook
+        for direntry in scandir(self.path):
+            if direntry.is_dir():
+                path = Path(direntry.path)
+                if not self.session.isinitpath(path, with_parents=True):
+                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
+                        continue
+                col = ihook.pytest_collect_directory(path=path, parent=self)
+                if col is not None:
+                    yield col
+
+            elif direntry.is_file():
+                path = Path(direntry.path)
+                if not self.session.isinitpath(path):
+                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
+                        continue
+                cols = ihook.pytest_collect_file(file_path=path, parent=self)
+                yield from cols
+
+
+@final
+class Session(nodes.Collector):
+    """The root of the collection tree.
+
+    ``Session`` collects the initial paths given as arguments to pytest.
+    """
+
     Interrupted = Interrupted
     Failed = Failed
     # Set on the session by runner.pytest_sessionstart.
     _setupstate: SetupState
     # Set on the session by fixtures.pytest_sessionstart.
     _fixturemanager: FixtureManager
-    exitstatus: Union[int, ExitCode]
+    exitstatus: int | ExitCode
 
     def __init__(self, config: Config) -> None:
         super().__init__(
+            name="",
             path=config.rootpath,
             fspath=None,
             parent=None,
@@ -471,28 +571,68 @@ def __init__(self, config: Config) -> None:
         )
         self.testsfailed = 0
         self.testscollected = 0
-        self.shouldstop: Union[bool, str] = False
-        self.shouldfail: Union[bool, str] = False
+        self._shouldstop: bool | str = False
+        self._shouldfail: bool | str = False
         self.trace = config.trace.root.get("collection")
-        self._initialpaths: FrozenSet[Path] = frozenset()
+        self._initialpaths: frozenset[Path] = frozenset()
+        self._initialpaths_with_parents: frozenset[Path] = frozenset()
+        self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = []
+        self._initial_parts: list[CollectionArgument] = []
+        self._collection_cache: dict[nodes.Collector, CollectReport] = {}
+        self.items: list[nodes.Item] = []
 
-        self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath)
+        self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath)
 
         self.config.pluginmanager.register(self, name="session")
 
     @classmethod
-    def from_config(cls, config: Config) -> "Session":
+    def from_config(cls, config: Config) -> Session:
         session: Session = cls._create(config=config)
         return session
 
     def __repr__(self) -> str:
-        return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
-            self.__class__.__name__,
-            self.name,
-            getattr(self, "exitstatus", "<UNSET>"),
-            self.testsfailed,
-            self.testscollected,
-        )
+        return (
+            f"<{self.__class__.__name__} {self.name} "
+            f"exitstatus=%r "
+            f"testsfailed={self.testsfailed} "
+            f"testscollected={self.testscollected}>"
+        ) % getattr(self, "exitstatus", "<UNSET>")
+
+    @property
+    def shouldstop(self) -> bool | str:
+        return self._shouldstop
+
+    @shouldstop.setter
+    def shouldstop(self, value: bool | str) -> None:
+        # The runner checks shouldfail and assumes that if it is set we are
+        # definitely stopping, so prevent unsetting it.
+        if value is False and self._shouldstop:
+            warnings.warn(
+                PytestWarning(
+                    "session.shouldstop cannot be unset after it has been set; ignoring."
+                ),
+                stacklevel=2,
+            )
+            return
+        self._shouldstop = value
+
+    @property
+    def shouldfail(self) -> bool | str:
+        return self._shouldfail
+
+    @shouldfail.setter
+    def shouldfail(self, value: bool | str) -> None:
+        # The runner checks shouldfail and assumes that if it is set we are
+        # definitely stopping, so prevent unsetting it.
+        if value is False and self._shouldfail:
+            warnings.warn(
+                PytestWarning(
+                    "session.shouldfail cannot be unset after it has been set; ignoring."
+                ),
+                stacklevel=2,
+            )
+            return
+        self._shouldfail = value
 
     @property
     def startpath(self) -> Path:
@@ -514,99 +654,103 @@ def pytest_collectstart(self) -> None:
             raise self.Interrupted(self.shouldstop)
 
     @hookimpl(tryfirst=True)
-    def pytest_runtest_logreport(
-        self, report: Union[TestReport, CollectReport]
-    ) -> None:
+    def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None:
         if report.failed and not hasattr(report, "wasxfail"):
             self.testsfailed += 1
             maxfail = self.config.getvalue("maxfail")
             if maxfail and self.testsfailed >= maxfail:
-                self.shouldfail = "stopping after %d failures" % (self.testsfailed)
+                self.shouldfail = f"stopping after {self.testsfailed} failures"
 
     pytest_collectreport = pytest_runtest_logreport
 
-    def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool:
+    def isinitpath(
+        self,
+        path: str | os.PathLike[str],
+        *,
+        with_parents: bool = False,
+    ) -> bool:
+        """Is path an initial path?
+
+        An initial path is a path explicitly given to pytest on the command
+        line.
+
+        :param with_parents:
+            If set, also return True if the path is a parent of an initial path.
+
+        .. versionchanged:: 8.0
+            Added the ``with_parents`` parameter.
+        """
         # Optimization: Path(Path(...)) is much slower than isinstance.
         path_ = path if isinstance(path, Path) else Path(path)
-        return path_ in self._initialpaths
+        if with_parents:
+            return path_ in self._initialpaths_with_parents
+        else:
+            return path_ in self._initialpaths
 
-    def gethookproxy(self, fspath: "os.PathLike[str]"):
+    def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay:
         # Optimization: Path(Path(...)) is much slower than isinstance.
         path = fspath if isinstance(fspath, Path) else Path(fspath)
         pm = self.config.pluginmanager
         # Check if we have the common case of running
         # hooks with all conftest.py files.
-        my_conftestmodules = pm._getconftestmodules(
-            path,
-            self.config.getoption("importmode"),
-            rootpath=self.config.rootpath,
-        )
+        my_conftestmodules = pm._getconftestmodules(path)
         remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
+        proxy: pluggy.HookRelay
         if remove_mods:
-            # One or more conftests are not in use at this fspath.
-            from .config.compat import PathAwareHookProxy
-
-            proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods))
+            # One or more conftests are not in use at this path.
+            proxy = PathAwareHookProxy(FSHookProxy(pm, remove_mods))  # type: ignore[arg-type,assignment]
         else:
             # All plugins are active for this fspath.
             proxy = self.config.hook
         return proxy
 
-    def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
-        if direntry.name == "__pycache__":
-            return False
-        fspath = Path(direntry.path)
-        ihook = self.gethookproxy(fspath.parent)
-        if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
-            return False
-        norecursepatterns = self.config.getini("norecursedirs")
-        if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns):
-            return False
-        return True
-
-    def _collectfile(
-        self, fspath: Path, handle_dupes: bool = True
+    def _collect_path(
+        self,
+        path: Path,
+        path_cache: dict[Path, Sequence[nodes.Collector]],
     ) -> Sequence[nodes.Collector]:
-        assert (
-            fspath.is_file()
-        ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
-            fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink()
-        )
-        ihook = self.gethookproxy(fspath)
-        if not self.isinitpath(fspath):
-            if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
-                return ()
+        """Create a Collector for the given path.
 
-        if handle_dupes:
-            keepduplicates = self.config.getoption("keepduplicates")
-            if not keepduplicates:
-                duplicate_paths = self.config.pluginmanager._duplicatepaths
-                if fspath in duplicate_paths:
-                    return ()
-                else:
-                    duplicate_paths.add(fspath)
+        `path_cache` makes it so the same Collectors are returned for the same
+        path.
+        """
+        if path in path_cache:
+            return path_cache[path]
+
+        if path.is_dir():
+            ihook = self.gethookproxy(path.parent)
+            col: nodes.Collector | None = ihook.pytest_collect_directory(
+                path=path, parent=self
+            )
+            cols: Sequence[nodes.Collector] = (col,) if col is not None else ()
+
+        elif path.is_file():
+            ihook = self.gethookproxy(path)
+            cols = ihook.pytest_collect_file(file_path=path, parent=self)
+
+        else:
+            # Broken symlink or invalid/missing file.
+            cols = ()
 
-        return ihook.pytest_collect_file(file_path=fspath, parent=self)  # type: ignore[no-any-return]
+        path_cache[path] = cols
+        return cols
 
     @overload
     def perform_collect(
-        self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ...
-    ) -> Sequence[nodes.Item]:
-        ...
+        self, args: Sequence[str] | None = ..., genitems: Literal[True] = ...
+    ) -> Sequence[nodes.Item]: ...
 
     @overload
     def perform_collect(
-        self, args: Optional[Sequence[str]] = ..., genitems: bool = ...
-    ) -> Sequence[Union[nodes.Item, nodes.Collector]]:
-        ...
+        self, args: Sequence[str] | None = ..., genitems: bool = ...
+    ) -> Sequence[nodes.Item | nodes.Collector]: ...
 
     def perform_collect(
-        self, args: Optional[Sequence[str]] = None, genitems: bool = True
-    ) -> Sequence[Union[nodes.Item, nodes.Collector]]:
+        self, args: Sequence[str] | None = None, genitems: bool = True
+    ) -> Sequence[nodes.Item | nodes.Collector]:
         """Perform the collection phase for this session.
 
-        This is called by the default
-        :func:`pytest_collection <_pytest.hookspec.pytest_collection>` hook
+        This is called by the default :hook:`pytest_collection` hook
         implementation; see the documentation of this hook for more details.
         For testing purposes, it may also be called directly on a fresh
         ``Session``.
@@ -623,33 +767,44 @@ def perform_collect(
         self.trace("perform_collect", self, args)
         self.trace.root.indent += 1
 
-        self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = []
-        self._initial_parts: List[Tuple[Path, List[str]]] = []
-        self.items: List[nodes.Item] = []
-
         hook = self.config.hook
 
-        items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items
+        self._notfound = []
+        self._initial_parts = []
+        self._collection_cache = {}
+        self.items = []
+        items: Sequence[nodes.Item | nodes.Collector] = self.items
         try:
-            initialpaths: List[Path] = []
+            initialpaths: list[Path] = []
+            initialpaths_with_parents: list[Path] = []
             for arg in args:
-                fspath, parts = resolve_collection_argument(
+                collection_argument = resolve_collection_argument(
                     self.config.invocation_params.dir,
                     arg,
                     as_pypath=self.config.option.pyargs,
                 )
-                self._initial_parts.append((fspath, parts))
-                initialpaths.append(fspath)
+                self._initial_parts.append(collection_argument)
+                initialpaths.append(collection_argument.path)
+                initialpaths_with_parents.append(collection_argument.path)
+                initialpaths_with_parents.extend(collection_argument.path.parents)
             self._initialpaths = frozenset(initialpaths)
+            self._initialpaths_with_parents = frozenset(initialpaths_with_parents)
+
             rep = collect_one_node(self)
             self.ihook.pytest_collectreport(report=rep)
             self.trace.root.indent -= 1
             if self._notfound:
                 errors = []
-                for arg, cols in self._notfound:
-                    line = f"(no name {arg!r} in any of {cols!r})"
-                    errors.append(f"not found: {arg}\n{line}")
+                for arg, collectors in self._notfound:
+                    if collectors:
+                        errors.append(
+                            f"not found: {arg}\n(no match in any of {collectors!r})"
+                        )
+                    else:
+                        errors.append(f"found no collectors for {arg}")
+
                 raise UsageError(*errors)
+
             if not genitems:
                 items = rep.result
             else:
@@ -662,194 +817,200 @@ def perform_collect(
                 session=self, config=self.config, items=items
             )
         finally:
+            self._notfound = []
+            self._initial_parts = []
+            self._collection_cache = {}
             hook.pytest_collection_finish(session=self)
 
-        self.testscollected = len(items)
-        return items
+        if genitems:
+            self.testscollected = len(items)
 
-    def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
-        from _pytest.python import Package
+        return items
 
-        # Keep track of any collected nodes in here, so we don't duplicate fixtures.
-        node_cache1: Dict[Path, Sequence[nodes.Collector]] = {}
-        node_cache2: Dict[Tuple[Type[nodes.Collector], Path], nodes.Collector] = {}
+    def _collect_one_node(
+        self,
+        node: nodes.Collector,
+        handle_dupes: bool = True,
+    ) -> tuple[CollectReport, bool]:
+        if node in self._collection_cache and handle_dupes:
+            rep = self._collection_cache[node]
+            return rep, True
+        else:
+            rep = collect_one_node(node)
+            self._collection_cache[node] = rep
+            return rep, False
 
-        # Keep track of any collected collectors in matchnodes paths, so they
-        # are not collected more than once.
-        matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {}
+    def collect(self) -> Iterator[nodes.Item | nodes.Collector]:
+        # This is a cache for the root directories of the initial paths.
+        # We can't use collection_cache for Session because of its special
+        # role as the bootstrapping collector.
+        path_cache: dict[Path, Sequence[nodes.Collector]] = {}
 
-        # Dirnames of pkgs with dunder-init files.
-        pkg_roots: Dict[str, Package] = {}
+        pm = self.config.pluginmanager
 
-        for argpath, names in self._initial_parts:
-            self.trace("processing argument", (argpath, names))
+        for collection_argument in self._initial_parts:
+            self.trace("processing argument", collection_argument)
             self.trace.root.indent += 1
 
-            # Start with a Session root, and delve to argpath item (dir or file)
-            # and stack all Packages found on the way.
-            # No point in finding packages when collecting doctests.
-            if not self.config.getoption("doctestmodules", False):
-                pm = self.config.pluginmanager
-                confcutdir = pm._confcutdir
-                for parent in (argpath, *argpath.parents):
-                    if confcutdir and parent in confcutdir.parents:
-                        break
+            argpath = collection_argument.path
+            names = collection_argument.parts
+            module_name = collection_argument.module_name
 
-                    if parent.is_dir():
-                        pkginit = parent / "__init__.py"
-                        if pkginit.is_file() and pkginit not in node_cache1:
-                            col = self._collectfile(pkginit, handle_dupes=False)
-                            if col:
-                                if isinstance(col[0], Package):
-                                    pkg_roots[str(parent)] = col[0]
-                                node_cache1[col[0].path] = [col[0]]
-
-            # If it's a directory argument, recurse and look for any Subpackages.
-            # Let the Package collector deal with subnodes, don't collect here.
+            # resolve_collection_argument() ensures this.
             if argpath.is_dir():
                 assert not names, f"invalid arg {(argpath, names)!r}"
 
-                seen_dirs: Set[Path] = set()
-                for direntry in visit(str(argpath), self._recurse):
-                    if not direntry.is_file():
-                        continue
-
-                    path = Path(direntry.path)
-                    dirpath = path.parent
-
-                    if dirpath not in seen_dirs:
-                        # Collect packages first.
-                        seen_dirs.add(dirpath)
-                        pkginit = dirpath / "__init__.py"
-                        if pkginit.exists():
-                            for x in self._collectfile(pkginit):
-                                yield x
-                                if isinstance(x, Package):
-                                    pkg_roots[str(dirpath)] = x
-                    if str(dirpath) in pkg_roots:
-                        # Do not collect packages here.
-                        continue
-
-                    for x in self._collectfile(path):
-                        key2 = (type(x), x.path)
-                        if key2 in node_cache2:
-                            yield node_cache2[key2]
-                        else:
-                            node_cache2[key2] = x
-                            yield x
+            paths = [argpath]
+            # Add relevant parents of the path, from the root, e.g.
+            #   /a/b/c.py -> [/, /a, /a/b, /a/b/c.py]
+            if module_name is None:
+                # Paths outside of the confcutdir should not be considered.
+                for path in argpath.parents:
+                    if not pm._is_in_confcutdir(path):
+                        break
+                    paths.insert(0, path)
             else:
-                assert argpath.is_file()
-
-                if argpath in node_cache1:
-                    col = node_cache1[argpath]
-                else:
-                    collect_root = pkg_roots.get(str(argpath.parent), self)
-                    col = collect_root._collectfile(argpath, handle_dupes=False)
-                    if col:
-                        node_cache1[argpath] = col
-
-                matching = []
-                work: List[
-                    Tuple[Sequence[Union[nodes.Item, nodes.Collector]], Sequence[str]]
-                ] = [(col, names)]
-                while work:
-                    self.trace("matchnodes", col, names)
-                    self.trace.root.indent += 1
-
-                    matchnodes, matchnames = work.pop()
-                    for node in matchnodes:
-                        if not matchnames:
-                            matching.append(node)
-                            continue
-                        if not isinstance(node, nodes.Collector):
-                            continue
-                        key = (type(node), node.nodeid)
-                        if key in matchnodes_cache:
-                            rep = matchnodes_cache[key]
-                        else:
-                            rep = collect_one_node(node)
-                            matchnodes_cache[key] = rep
-                        if rep.passed:
-                            submatchnodes = []
-                            for r in rep.result:
-                                # TODO: Remove parametrized workaround once collection structure contains
-                                # parametrization.
-                                if (
-                                    r.name == matchnames[0]
-                                    or r.name.split("[")[0] == matchnames[0]
-                                ):
-                                    submatchnodes.append(r)
-                            if submatchnodes:
-                                work.append((submatchnodes, matchnames[1:]))
-                        else:
-                            # Report collection failures here to avoid failing to run some test
-                            # specified in the command line because the module could not be
-                            # imported (#134).
-                            node.ihook.pytest_collectreport(report=rep)
-
-                    self.trace("matchnodes finished -> ", len(matching), "nodes")
-                    self.trace.root.indent -= 1
-
-                if not matching:
-                    report_arg = "::".join((str(argpath), *names))
-                    self._notfound.append((report_arg, col))
+                # For --pyargs arguments, only consider paths matching the module
+                # name. Paths beyond the package hierarchy are not included.
+                module_name_parts = module_name.split(".")
+                for i, path in enumerate(argpath.parents, 2):
+                    if i > len(module_name_parts) or path.stem != module_name_parts[-i]:
+                        break
+                    paths.insert(0, path)
+
+            # Start going over the parts from the root, collecting each level
+            # and discarding all nodes which don't match the level's part.
+            any_matched_in_initial_part = False
+            notfound_collectors = []
+            work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [
+                (self, [*paths, *names])
+            ]
+            while work:
+                matchnode, matchparts = work.pop()
+
+                # Pop'd all of the parts, this is a match.
+                if not matchparts:
+                    yield matchnode
+                    any_matched_in_initial_part = True
                     continue
 
-                # If __init__.py was the only file requested, then the matched
-                # node will be the corresponding Package (by default), and the
-                # first yielded item will be the __init__ Module itself, so
-                # just use that. If this special case isn't taken, then all the
-                # files in the package will be yielded.
-                if argpath.name == "__init__.py" and isinstance(matching[0], Package):
-                    try:
-                        yield next(iter(matching[0].collect()))
-                    except StopIteration:
-                        # The package collects nothing with only an __init__.py
-                        # file in it, which gets ignored by the default
-                        # "python_files" option.
-                        pass
+                # Should have been matched by now, discard.
+                if not isinstance(matchnode, nodes.Collector):
                     continue
 
-                yield from matching
+                # Collect this level of matching.
+                # Collecting Session (self) is done directly to avoid endless
+                # recursion to this function.
+                subnodes: Sequence[nodes.Collector | nodes.Item]
+                if isinstance(matchnode, Session):
+                    assert isinstance(matchparts[0], Path)
+                    subnodes = matchnode._collect_path(matchparts[0], path_cache)
+                else:
+                    # For backward compat, files given directly multiple
+                    # times on the command line should not be deduplicated.
+                    handle_dupes = not (
+                        len(matchparts) == 1
+                        and isinstance(matchparts[0], Path)
+                        and matchparts[0].is_file()
+                    )
+                    rep, duplicate = self._collect_one_node(matchnode, handle_dupes)
+                    if not duplicate and not rep.passed:
+                        # Report collection failures here to avoid failing to
+                        # run some test specified in the command line because
+                        # the module could not be imported (#134).
+                        matchnode.ihook.pytest_collectreport(report=rep)
+                    if not rep.passed:
+                        continue
+                    subnodes = rep.result
+
+                # Prune this level.
+                any_matched_in_collector = False
+                for node in reversed(subnodes):
+                    # Path part e.g. `/a/b/` in `/a/b/test_file.py::TestIt::test_it`.
+                    if isinstance(matchparts[0], Path):
+                        is_match = node.path == matchparts[0]
+                        if sys.platform == "win32" and not is_match:
+                            # In case the file paths do not match, fallback to samefile() to
+                            # account for short-paths on Windows (#11895).
+                            same_file = os.path.samefile(node.path, matchparts[0])
+                            # We don't want to match links to the current node,
+                            # otherwise we would match the same file more than once (#12039).
+                            is_match = same_file and (
+                                os.path.islink(node.path)
+                                == os.path.islink(matchparts[0])
+                            )
+
+                    # Name part e.g. `TestIt` in `/a/b/test_file.py::TestIt::test_it`.
+                    else:
+                        # TODO: Remove parametrized workaround once collection structure contains
+                        # parametrization.
+                        is_match = (
+                            node.name == matchparts[0]
+                            or node.name.split("[")[0] == matchparts[0]
+                        )
+                    if is_match:
+                        work.append((node, matchparts[1:]))
+                        any_matched_in_collector = True
+
+                if not any_matched_in_collector:
+                    notfound_collectors.append(matchnode)
+
+            if not any_matched_in_initial_part:
+                report_arg = "::".join((str(argpath), *names))
+                self._notfound.append((report_arg, notfound_collectors))
 
             self.trace.root.indent -= 1
 
-    def genitems(
-        self, node: Union[nodes.Item, nodes.Collector]
-    ) -> Iterator[nodes.Item]:
+    def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]:
         self.trace("genitems", node)
         if isinstance(node, nodes.Item):
             node.ihook.pytest_itemcollected(item=node)
             yield node
         else:
             assert isinstance(node, nodes.Collector)
-            rep = collect_one_node(node)
+            keepduplicates = self.config.getoption("keepduplicates")
+            # For backward compat, dedup only applies to files.
+            handle_dupes = not (keepduplicates and isinstance(node, nodes.File))
+            rep, duplicate = self._collect_one_node(node, handle_dupes)
+            if duplicate and not keepduplicates:
+                return
             if rep.passed:
                 for subnode in rep.result:
                     yield from self.genitems(subnode)
-            node.ihook.pytest_collectreport(report=rep)
+            if not duplicate:
+                node.ihook.pytest_collectreport(report=rep)
 
 
-def search_pypath(module_name: str) -> str:
-    """Search sys.path for the given a dotted module name, and return its file system path."""
+def search_pypath(module_name: str) -> str | None:
+    """Search sys.path for the given a dotted module name, and return its file
+    system path if found."""
     try:
         spec = importlib.util.find_spec(module_name)
     # AttributeError: looks like package module, but actually filename
     # ImportError: module does not exist
     # ValueError: not a module name
     except (AttributeError, ImportError, ValueError):
-        return module_name
+        return None
     if spec is None or spec.origin is None or spec.origin == "namespace":
-        return module_name
+        return None
     elif spec.submodule_search_locations:
         return os.path.dirname(spec.origin)
     else:
         return spec.origin
 
 
+@dataclasses.dataclass(frozen=True)
+class CollectionArgument:
+    """A resolved collection argument."""
+
+    path: Path
+    parts: Sequence[str]
+    module_name: str | None
+
+
 def resolve_collection_argument(
     invocation_path: Path, arg: str, *, as_pypath: bool = False
-) -> Tuple[Path, List[str]]:
+) -> CollectionArgument:
     """Parse path arguments optionally containing selection parts and return (fspath, names).
 
     Command-line arguments can point to files and/or directories, and optionally contain
@@ -857,9 +1018,13 @@ def resolve_collection_argument(
 
         "pkg/tests/test_foo.py::TestClass::test_foo"
 
-    This function ensures the path exists, and returns a tuple:
+    This function ensures the path exists, and returns a resolved `CollectionArgument`:
 
-        (Path("/full/path/to/pkg/tests/test_foo.py"), ["TestClass", "test_foo"])
+        CollectionArgument(
+            path=Path("/full/path/to/pkg/tests/test_foo.py"),
+            parts=["TestClass", "test_foo"],
+            module_name=None,
+        )
 
     When as_pypath is True, expects that the command-line argument actually contains
     module paths instead of file-system paths:
@@ -867,17 +1032,30 @@ def resolve_collection_argument(
         "pkg.tests.test_foo::TestClass::test_foo"
 
     In which case we search sys.path for a matching module, and then return the *path* to the
-    found module.
+    found module, which may look like this:
+
+        CollectionArgument(
+            path=Path("/home/u/myvenv/lib/site-packages/pkg/tests/test_foo.py"),
+            parts=["TestClass", "test_foo"],
+            module_name="pkg.tests.test_foo",
+        )
 
     If the path doesn't exist, raise UsageError.
     If the path is a directory and selection parts are present, raise UsageError.
     """
-    strpath, *parts = str(arg).split("::")
+    base, squacket, rest = str(arg).partition("[")
+    strpath, *parts = base.split("::")
+    if parts:
+        parts[-1] = f"{parts[-1]}{squacket}{rest}"
+    module_name = None
     if as_pypath:
-        strpath = search_pypath(strpath)
+        pyarg_strpath = search_pypath(strpath)
+        if pyarg_strpath is not None:
+            module_name = strpath
+            strpath = pyarg_strpath
     fspath = invocation_path / strpath
     fspath = absolutepath(fspath)
-    if not fspath.exists():
+    if not safe_exists(fspath):
         msg = (
             "module or package not found: {arg} (missing __init__.py?)"
             if as_pypath
@@ -891,4 +1069,8 @@ def resolve_collection_argument(
             else "directory argument cannot contain :: selection parts: {arg}"
         )
         raise UsageError(msg.format(arg=arg))
-    return fspath, parts
+    return CollectionArgument(
+        path=fspath,
+        parts=parts,
+        module_name=module_name,
+    )
diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py
index 7e082f2e6e0..068c7410a46 100644
--- a/src/_pytest/mark/__init__.py
+++ b/src/_pytest/mark/__init__.py
@@ -1,18 +1,21 @@
 """Generic mechanism for marking and selecting python functions."""
-import warnings
-from typing import AbstractSet
-from typing import Collection
-from typing import List
+
+from __future__ import annotations
+
+import collections
+from collections.abc import Collection
+from collections.abc import Iterable
+from collections.abc import Set as AbstractSet
+import dataclasses
 from typing import Optional
 from typing import TYPE_CHECKING
-from typing import Union
-
-import attr
 
 from .expression import Expression
 from .expression import ParseError
+from .structures import _HiddenParam
 from .structures import EMPTY_PARAMETERSET_OPTION
 from .structures import get_empty_parameterset_mark
+from .structures import HIDDEN_PARAM
 from .structures import Mark
 from .structures import MARK_GEN
 from .structures import MarkDecorator
@@ -22,16 +25,17 @@
 from _pytest.config import ExitCode
 from _pytest.config import hookimpl
 from _pytest.config import UsageError
+from _pytest.config.argparsing import NOT_SET
 from _pytest.config.argparsing import Parser
-from _pytest.deprecated import MINUS_K_COLON
-from _pytest.deprecated import MINUS_K_DASH
 from _pytest.stash import StashKey
 
+
 if TYPE_CHECKING:
     from _pytest.nodes import Item
 
 
 __all__ = [
+    "HIDDEN_PARAM",
     "MARK_GEN",
     "Mark",
     "MarkDecorator",
@@ -46,8 +50,8 @@
 
 def param(
     *values: object,
-    marks: Union[MarkDecorator, Collection[Union[MarkDecorator, Mark]]] = (),
-    id: Optional[str] = None,
+    marks: MarkDecorator | Collection[MarkDecorator | Mark] = (),
+    id: str | _HiddenParam | None = None,
 ) -> ParameterSet:
     """Specify a parameter in `pytest.mark.parametrize`_ calls or
     :ref:`parametrized fixtures <fixture-parametrize-marks>`.
@@ -65,22 +69,34 @@ def test_eval(test_input, expected):
             assert eval(test_input) == expected
 
     :param values: Variable args of the values of the parameter set, in order.
-    :keyword marks: A single mark or a list of marks to be applied to this parameter set.
-    :keyword str id: The id to attribute to this parameter set.
+
+    :param marks:
+        A single mark or a list of marks to be applied to this parameter set.
+
+        :ref:`pytest.mark.usefixtures <pytest.mark.usefixtures ref>` cannot be added via this parameter.
+
+    :type id: str | Literal[pytest.HIDDEN_PARAM] | None
+    :param id:
+        The id to attribute to this parameter set.
+
+        .. versionadded:: 8.4
+            :ref:`hidden-param` means to hide the parameter set
+            from the test name. Can only be used at most 1 time, as
+            test names need to be unique.
     """
     return ParameterSet.param(*values, marks=marks, id=id)
 
 
 def pytest_addoption(parser: Parser) -> None:
     group = parser.getgroup("general")
-    group._addoption(
+    group._addoption(  # private to use reserved lower-case short option
         "-k",
         action="store",
         dest="keyword",
         default="",
         metavar="EXPRESSION",
-        help="only run tests which match the given substring expression. "
-        "An expression is a python evaluatable expression "
+        help="Only run tests which match the given substring expression. "
+        "An expression is a Python evaluable expression "
         "where all names are substring-matched against test names "
         "and their parent classes. Example: -k 'test_method or test_"
         "other' matches all test functions and classes whose name "
@@ -93,13 +109,13 @@ def pytest_addoption(parser: Parser) -> None:
         "The matching is case-insensitive.",
     )
 
-    group._addoption(
+    group._addoption(  # private to use reserved lower-case short option
         "-m",
         action="store",
         dest="markexpr",
         default="",
         metavar="MARKEXPR",
-        help="only run tests matching given mark expression.\n"
+        help="Only run tests matching given mark expression. "
         "For example: -m 'mark1 and not mark2'.",
     )
 
@@ -109,12 +125,12 @@ def pytest_addoption(parser: Parser) -> None:
         help="show markers (builtin, plugin and per-project ones).",
     )
 
-    parser.addini("markers", "markers for test functions", "linelist")
-    parser.addini(EMPTY_PARAMETERSET_OPTION, "default marker for empty parametersets")
+    parser.addini("markers", "Register new markers for test functions", "linelist")
+    parser.addini(EMPTY_PARAMETERSET_OPTION, "Default marker for empty parametersets")
 
 
 @hookimpl(tryfirst=True)
-def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
+def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
     import _pytest.config
 
     if config.option.markers:
@@ -124,7 +140,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
             parts = line.split(":", 1)
             name = parts[0]
             rest = parts[1] if len(parts) == 2 else ""
-            tw.write("@pytest.mark.%s:" % name, bold=True)
+            tw.write(f"@pytest.mark.{name}:", bold=True)
             tw.line(rest)
             tw.line()
         config._ensure_unconfigure()
@@ -133,7 +149,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
     return None
 
 
-@attr.s(slots=True, auto_attribs=True)
+@dataclasses.dataclass
 class KeywordMatcher:
     """A matcher for keywords.
 
@@ -148,18 +164,27 @@ class KeywordMatcher:
     any item, as well as names directly assigned to test functions.
     """
 
+    __slots__ = ("_names",)
+
     _names: AbstractSet[str]
 
     @classmethod
-    def from_item(cls, item: "Item") -> "KeywordMatcher":
+    def from_item(cls, item: Item) -> KeywordMatcher:
         mapped_names = set()
 
-        # Add the names of the current item and any parent items.
+        # Add the names of the current item and any parent items,
+        # except the Session and root Directory's which are not
+        # interesting for matching.
         import pytest
 
         for node in item.listchain():
-            if not isinstance(node, pytest.Session):
-                mapped_names.add(node.name)
+            if isinstance(node, pytest.Session):
+                continue
+            if isinstance(node, pytest.Directory) and isinstance(
+                node.parent, pytest.Session
+            ):
+                continue
+            mapped_names.add(node.name)
 
         # Add the names added as extra keywords to current or parent items.
         mapped_names.update(item.listextrakeywords())
@@ -174,42 +199,26 @@ def from_item(cls, item: "Item") -> "KeywordMatcher":
 
         return cls(mapped_names)
 
-    def __call__(self, subname: str) -> bool:
+    def __call__(self, subname: str, /, **kwargs: str | int | bool | None) -> bool:
+        if kwargs:
+            raise UsageError("Keyword expressions do not support call parameters.")
         subname = subname.lower()
-        names = (name.lower() for name in self._names)
+        return any(subname in name.lower() for name in self._names)
 
-        for name in names:
-            if subname in name:
-                return True
-        return False
 
-
-def deselect_by_keyword(items: "List[Item]", config: Config) -> None:
+def deselect_by_keyword(items: list[Item], config: Config) -> None:
     keywordexpr = config.option.keyword.lstrip()
     if not keywordexpr:
         return
 
-    if keywordexpr.startswith("-"):
-        # To be removed in pytest 8.0.0.
-        warnings.warn(MINUS_K_DASH, stacklevel=2)
-        keywordexpr = "not " + keywordexpr[1:]
-    selectuntil = False
-    if keywordexpr[-1:] == ":":
-        # To be removed in pytest 8.0.0.
-        warnings.warn(MINUS_K_COLON, stacklevel=2)
-        selectuntil = True
-        keywordexpr = keywordexpr[:-1]
-
     expr = _parse_expression(keywordexpr, "Wrong expression passed to '-k'")
 
     remaining = []
     deselected = []
     for colitem in items:
-        if keywordexpr and not expr.evaluate(KeywordMatcher.from_item(colitem)):
+        if not expr.evaluate(KeywordMatcher.from_item(colitem)):
             deselected.append(colitem)
         else:
-            if selectuntil:
-                keywordexpr = None
             remaining.append(colitem)
 
     if deselected:
@@ -217,34 +226,44 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None:
         items[:] = remaining
 
 
-@attr.s(slots=True, auto_attribs=True)
+@dataclasses.dataclass
 class MarkMatcher:
     """A matcher for markers which are present.
 
     Tries to match on any marker names, attached to the given colitem.
     """
 
-    own_mark_names: AbstractSet[str]
+    __slots__ = ("own_mark_name_mapping",)
 
-    @classmethod
-    def from_item(cls, item: "Item") -> "MarkMatcher":
-        mark_names = {mark.name for mark in item.iter_markers()}
-        return cls(mark_names)
+    own_mark_name_mapping: dict[str, list[Mark]]
 
-    def __call__(self, name: str) -> bool:
-        return name in self.own_mark_names
+    @classmethod
+    def from_markers(cls, markers: Iterable[Mark]) -> MarkMatcher:
+        mark_name_mapping = collections.defaultdict(list)
+        for mark in markers:
+            mark_name_mapping[mark.name].append(mark)
+        return cls(mark_name_mapping)
+
+    def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool:
+        if not (matches := self.own_mark_name_mapping.get(name, [])):
+            return False
+
+        for mark in matches:  # pylint: disable=consider-using-any-or-all
+            if all(mark.kwargs.get(k, NOT_SET) == v for k, v in kwargs.items()):
+                return True
+        return False
 
 
-def deselect_by_mark(items: "List[Item]", config: Config) -> None:
+def deselect_by_mark(items: list[Item], config: Config) -> None:
     matchexpr = config.option.markexpr
     if not matchexpr:
         return
 
     expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'")
-    remaining: List[Item] = []
-    deselected: List[Item] = []
+    remaining: list[Item] = []
+    deselected: list[Item] = []
     for item in items:
-        if expr.evaluate(MarkMatcher.from_item(item)):
+        if expr.evaluate(MarkMatcher.from_markers(item.iter_markers())):
             remaining.append(item)
         else:
             deselected.append(item)
@@ -260,7 +279,7 @@ def _parse_expression(expr: str, exc_message: str) -> Expression:
         raise UsageError(f"{exc_message}: {expr}: {e}") from None
 
 
-def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None:
+def pytest_collection_modifyitems(items: list[Item], config: Config) -> None:
     deselect_by_keyword(items, config)
     deselect_by_mark(items, config)
 
@@ -273,8 +292,8 @@ def pytest_configure(config: Config) -> None:
 
     if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""):
         raise UsageError(
-            "{!s} must be one of skip, xfail or fail_at_collect"
-            " but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)
+            f"{EMPTY_PARAMETERSET_OPTION!s} must be one of skip, xfail or fail_at_collect"
+            f" but it is {empty_parameterset!r}"
         )
 
 
diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py
index 9d57e944b92..b71ed29c62f 100644
--- a/src/_pytest/mark/expression.py
+++ b/src/_pytest/mark/expression.py
@@ -5,30 +5,36 @@
 expression: expr? EOF
 expr:       and_expr ('or' and_expr)*
 and_expr:   not_expr ('and' not_expr)*
-not_expr:   'not' not_expr | '(' expr ')' | ident
+not_expr:   'not' not_expr | '(' expr ')' | ident kwargs?
+
 ident:      (\w|:|\+|-|\.|\[|\]|\\|/)+
+kwargs:     ('(' name '=' value ( ', ' name '=' value )*  ')')
+name:       a valid ident, but not a reserved keyword
+value:      (unescaped) string literal | (-)?[0-9]+ | 'False' | 'True' | 'None'
 
 The semantics are:
 
 - Empty expression evaluates to False.
-- ident evaluates to True of False according to a provided matcher function.
+- ident evaluates to True or False according to a provided matcher function.
 - or/and/not evaluate according to the usual boolean semantics.
+- ident with parentheses and keyword arguments evaluates to True or False according to a provided matcher function.
 """
+
+from __future__ import annotations
+
 import ast
+from collections.abc import Iterator
+from collections.abc import Mapping
+from collections.abc import Sequence
+import dataclasses
 import enum
+import keyword
 import re
 import types
-from typing import Callable
-from typing import Iterator
-from typing import Mapping
-from typing import Optional
-from typing import Sequence
-from typing import TYPE_CHECKING
-
-import attr
-
-if TYPE_CHECKING:
-    from typing import NoReturn
+from typing import Literal
+from typing import NoReturn
+from typing import overload
+from typing import Protocol
 
 
 __all__ = [
@@ -45,10 +51,14 @@ class TokenType(enum.Enum):
     NOT = "not"
     IDENT = "identifier"
     EOF = "end of input"
+    EQUAL = "="
+    STRING = "string literal"
+    COMMA = ","
 
 
-@attr.s(frozen=True, slots=True, auto_attribs=True)
+@dataclasses.dataclass(frozen=True)
 class Token:
+    __slots__ = ("pos", "type", "value")
     type: TokenType
     value: str
     pos: int
@@ -70,7 +80,7 @@ def __str__(self) -> str:
 
 
 class Scanner:
-    __slots__ = ("tokens", "current")
+    __slots__ = ("current", "tokens")
 
     def __init__(self, input: str) -> None:
         self.tokens = self.lex(input)
@@ -87,6 +97,27 @@ def lex(self, input: str) -> Iterator[Token]:
             elif input[pos] == ")":
                 yield Token(TokenType.RPAREN, ")", pos)
                 pos += 1
+            elif input[pos] == "=":
+                yield Token(TokenType.EQUAL, "=", pos)
+                pos += 1
+            elif input[pos] == ",":
+                yield Token(TokenType.COMMA, ",", pos)
+                pos += 1
+            elif (quote_char := input[pos]) in ("'", '"'):
+                end_quote_pos = input.find(quote_char, pos + 1)
+                if end_quote_pos == -1:
+                    raise ParseError(
+                        pos + 1,
+                        f'closing quote "{quote_char}" is missing',
+                    )
+                value = input[pos : end_quote_pos + 1]
+                if (backslash_pos := input.find("\\")) != -1:
+                    raise ParseError(
+                        backslash_pos + 1,
+                        r'escaping with "\" not supported in marker expression',
+                    )
+                yield Token(TokenType.STRING, value, pos)
+                pos += len(value)
             else:
                 match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\|/)+", input[pos:])
                 if match:
@@ -107,7 +138,15 @@ def lex(self, input: str) -> Iterator[Token]:
                     )
         yield Token(TokenType.EOF, "", pos)
 
-    def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]:
+    @overload
+    def accept(self, type: TokenType, *, reject: Literal[True]) -> Token: ...
+
+    @overload
+    def accept(
+        self, type: TokenType, *, reject: Literal[False] = False
+    ) -> Token | None: ...
+
+    def accept(self, type: TokenType, *, reject: bool = False) -> Token | None:
         if self.current.type is type:
             token = self.current
             if token.type is not TokenType.EOF:
@@ -117,7 +156,7 @@ def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]:
             self.reject((type,))
         return None
 
-    def reject(self, expected: Sequence[TokenType]) -> "NoReturn":
+    def reject(self, expected: Sequence[TokenType]) -> NoReturn:
         raise ParseError(
             self.current.pos + 1,
             "expected {}; got {}".format(
@@ -135,7 +174,7 @@ def reject(self, expected: Sequence[TokenType]) -> "NoReturn":
 
 def expression(s: Scanner) -> ast.Expression:
     if s.accept(TokenType.EOF):
-        ret: ast.expr = ast.NameConstant(False)
+        ret: ast.expr = ast.Constant(False)
     else:
         ret = expr(s)
         s.accept(TokenType.EOF, reject=True)
@@ -167,18 +206,85 @@ def not_expr(s: Scanner) -> ast.expr:
         return ret
     ident = s.accept(TokenType.IDENT)
     if ident:
-        return ast.Name(IDENT_PREFIX + ident.value, ast.Load())
+        name = ast.Name(IDENT_PREFIX + ident.value, ast.Load())
+        if s.accept(TokenType.LPAREN):
+            ret = ast.Call(func=name, args=[], keywords=all_kwargs(s))
+            s.accept(TokenType.RPAREN, reject=True)
+        else:
+            ret = name
+        return ret
+
     s.reject((TokenType.NOT, TokenType.LPAREN, TokenType.IDENT))
 
 
-class MatcherAdapter(Mapping[str, bool]):
+BUILTIN_MATCHERS = {"True": True, "False": False, "None": None}
+
+
+def single_kwarg(s: Scanner) -> ast.keyword:
+    keyword_name = s.accept(TokenType.IDENT, reject=True)
+    if not keyword_name.value.isidentifier():
+        raise ParseError(
+            keyword_name.pos + 1,
+            f"not a valid python identifier {keyword_name.value}",
+        )
+    if keyword.iskeyword(keyword_name.value):
+        raise ParseError(
+            keyword_name.pos + 1,
+            f"unexpected reserved python keyword `{keyword_name.value}`",
+        )
+    s.accept(TokenType.EQUAL, reject=True)
+
+    if value_token := s.accept(TokenType.STRING):
+        value: str | int | bool | None = value_token.value[1:-1]  # strip quotes
+    else:
+        value_token = s.accept(TokenType.IDENT, reject=True)
+        if (number := value_token.value).isdigit() or (
+            number.startswith("-") and number[1:].isdigit()
+        ):
+            value = int(number)
+        elif value_token.value in BUILTIN_MATCHERS:
+            value = BUILTIN_MATCHERS[value_token.value]
+        else:
+            raise ParseError(
+                value_token.pos + 1,
+                f'unexpected character/s "{value_token.value}"',
+            )
+
+    ret = ast.keyword(keyword_name.value, ast.Constant(value))
+    return ret
+
+
+def all_kwargs(s: Scanner) -> list[ast.keyword]:
+    ret = [single_kwarg(s)]
+    while s.accept(TokenType.COMMA):
+        ret.append(single_kwarg(s))
+    return ret
+
+
+class MatcherCall(Protocol):
+    def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: ...
+
+
+@dataclasses.dataclass
+class MatcherNameAdapter:
+    matcher: MatcherCall
+    name: str
+
+    def __bool__(self) -> bool:
+        return self.matcher(self.name)
+
+    def __call__(self, **kwargs: str | int | bool | None) -> bool:
+        return self.matcher(self.name, **kwargs)
+
+
+class MatcherAdapter(Mapping[str, MatcherNameAdapter]):
     """Adapts a matcher function to a locals mapping as required by eval()."""
 
-    def __init__(self, matcher: Callable[[str], bool]) -> None:
+    def __init__(self, matcher: MatcherCall) -> None:
         self.matcher = matcher
 
-    def __getitem__(self, key: str) -> bool:
-        return self.matcher(key[len(IDENT_PREFIX) :])
+    def __getitem__(self, key: str) -> MatcherNameAdapter:
+        return MatcherNameAdapter(matcher=self.matcher, name=key[len(IDENT_PREFIX) :])
 
     def __iter__(self) -> Iterator[str]:
         raise NotImplementedError()
@@ -190,7 +296,7 @@ def __len__(self) -> int:
 class Expression:
     """A compiled match expression as used by -k and -m.
 
-    The expression can be evaulated against different matchers.
+    The expression can be evaluated against different matchers.
     """
 
     __slots__ = ("code",)
@@ -199,7 +305,7 @@ def __init__(self, code: types.CodeType) -> None:
         self.code = code
 
     @classmethod
-    def compile(self, input: str) -> "Expression":
+    def compile(self, input: str) -> Expression:
         """Compile a match expression.
 
         :param input: The input expression - one line.
@@ -212,7 +318,7 @@ def compile(self, input: str) -> "Expression":
         )
         return Expression(code)
 
-    def evaluate(self, matcher: Callable[[str], bool]) -> bool:
+    def evaluate(self, matcher: MatcherCall) -> bool:
         """Evaluate the match expression.
 
         :param matcher:
@@ -221,5 +327,5 @@ def evaluate(self, matcher: Callable[[str], bool]) -> bool:
 
         :returns: Whether the expression matches or not.
         """
-        ret: bool = eval(self.code, {"__builtins__": {}}, MatcherAdapter(matcher))
+        ret: bool = bool(eval(self.code, {"__builtins__": {}}, MatcherAdapter(matcher)))
         return ret
diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py
index 0e42cd8de5f..7a49b1a9b0c 100644
--- a/src/_pytest/mark/structures.py
+++ b/src/_pytest/mark/structures.py
@@ -1,37 +1,38 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import collections.abc
+from collections.abc import Callable
+from collections.abc import Collection
+from collections.abc import Iterable
+from collections.abc import Iterator
+from collections.abc import Mapping
+from collections.abc import MutableMapping
+from collections.abc import Sequence
+import dataclasses
+import enum
 import inspect
-import warnings
 from typing import Any
-from typing import Callable
-from typing import Collection
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Mapping
-from typing import MutableMapping
+from typing import final
 from typing import NamedTuple
-from typing import Optional
 from typing import overload
-from typing import Sequence
-from typing import Set
-from typing import Tuple
-from typing import Type
 from typing import TYPE_CHECKING
 from typing import TypeVar
 from typing import Union
-
-import attr
+import warnings
 
 from .._code import getfslineno
-from ..compat import ascii_escaped
-from ..compat import final
 from ..compat import NOTSET
 from ..compat import NotSetType
 from _pytest.config import Config
 from _pytest.deprecated import check_ispytest
+from _pytest.deprecated import MARKED_FIXTURE
 from _pytest.outcomes import fail
+from _pytest.raises import AbstractRaises
+from _pytest.scope import _ScopeName
 from _pytest.warning_types import PytestUnknownMarkWarning
 
+
 if TYPE_CHECKING:
     from ..nodes import Node
 
@@ -39,73 +40,79 @@
 EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
 
 
+# Singleton type for HIDDEN_PARAM, as described in:
+# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions
+class _HiddenParam(enum.Enum):
+    token = 0
+
+
+#: Can be used as a parameter set id to hide it from the test name.
+HIDDEN_PARAM = _HiddenParam.token
+
+
 def istestfunc(func) -> bool:
     return callable(func) and getattr(func, "__name__", "<lambda>") != "<lambda>"
 
 
 def get_empty_parameterset_mark(
     config: Config, argnames: Sequence[str], func
-) -> "MarkDecorator":
+) -> MarkDecorator:
     from ..nodes import Collector
 
-    fs, lineno = getfslineno(func)
-    reason = "got empty parameter set %r, function %s at %s:%d" % (
-        argnames,
-        func.__name__,
-        fs,
-        lineno,
-    )
+    argslisting = ", ".join(argnames)
 
+    fs, lineno = getfslineno(func)
+    reason = f"got empty parameter set for ({argslisting})"
     requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
     if requested_mark in ("", None, "skip"):
         mark = MARK_GEN.skip(reason=reason)
     elif requested_mark == "xfail":
         mark = MARK_GEN.xfail(reason=reason, run=False)
     elif requested_mark == "fail_at_collect":
-        f_name = func.__name__
-        _, lineno = getfslineno(func)
         raise Collector.CollectError(
-            "Empty parameter set in '%s' at line %d" % (f_name, lineno + 1)
+            f"Empty parameter set in '{func.__name__}' at line {lineno + 1}"
         )
     else:
         raise LookupError(requested_mark)
     return mark
 
 
-class ParameterSet(
-    NamedTuple(
-        "ParameterSet",
-        [
-            ("values", Sequence[Union[object, NotSetType]]),
-            ("marks", Collection[Union["MarkDecorator", "Mark"]]),
-            ("id", Optional[str]),
-        ],
-    )
-):
+class ParameterSet(NamedTuple):
+    values: Sequence[object | NotSetType]
+    marks: Collection[MarkDecorator | Mark]
+    id: str | _HiddenParam | None
+
     @classmethod
     def param(
         cls,
         *values: object,
-        marks: Union["MarkDecorator", Collection[Union["MarkDecorator", "Mark"]]] = (),
-        id: Optional[str] = None,
-    ) -> "ParameterSet":
+        marks: MarkDecorator | Collection[MarkDecorator | Mark] = (),
+        id: str | _HiddenParam | None = None,
+    ) -> ParameterSet:
         if isinstance(marks, MarkDecorator):
             marks = (marks,)
         else:
             assert isinstance(marks, collections.abc.Collection)
+        if any(i.name == "usefixtures" for i in marks):
+            raise ValueError(
+                "pytest.param cannot add pytest.mark.usefixtures; see "
+                "https://docs.pytest.org/en/stable/reference/reference.html#pytest-param"
+            )
 
         if id is not None:
-            if not isinstance(id, str):
-                raise TypeError(f"Expected id to be a string, got {type(id)}: {id!r}")
-            id = ascii_escaped(id)
+            if not isinstance(id, str) and id is not HIDDEN_PARAM:
+                raise TypeError(
+                    "Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, "
+                    f"got {type(id)}: {id!r}",
+                )
         return cls(values, marks, id)
 
     @classmethod
     def extract_from(
         cls,
-        parameterset: Union["ParameterSet", Sequence[object], object],
+        parameterset: ParameterSet | Sequence[object] | object,
         force_tuple: bool = False,
-    ) -> "ParameterSet":
+    ) -> ParameterSet:
         """Extract from an object or objects.
 
         :param parameterset:
@@ -116,7 +123,6 @@ def extract_from(
             Enforce tuple wrapping so single argument tuple values
             don't get decomposed and break tests.
         """
-
         if isinstance(parameterset, cls):
             return parameterset
         if force_tuple:
@@ -131,12 +137,12 @@ def extract_from(
 
     @staticmethod
     def _parse_parametrize_args(
-        argnames: Union[str, List[str], Tuple[str, ...]],
-        argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
+        argnames: str | Sequence[str],
+        argvalues: Iterable[ParameterSet | Sequence[object] | object],
         *args,
         **kwargs,
-    ) -> Tuple[Union[List[str], Tuple[str, ...]], bool]:
-        if not isinstance(argnames, (tuple, list)):
+    ) -> tuple[Sequence[str], bool]:
+        if isinstance(argnames, str):
             argnames = [x.strip() for x in argnames.split(",") if x.strip()]
             force_tuple = len(argnames) == 1
         else:
@@ -145,9 +151,9 @@ def _parse_parametrize_args(
 
     @staticmethod
     def _parse_parametrize_parameters(
-        argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
+        argvalues: Iterable[ParameterSet | Sequence[object] | object],
         force_tuple: bool,
-    ) -> List["ParameterSet"]:
+    ) -> list[ParameterSet]:
         return [
             ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
         ]
@@ -155,12 +161,12 @@ def _parse_parametrize_parameters(
     @classmethod
     def _for_parametrize(
         cls,
-        argnames: Union[str, List[str], Tuple[str, ...]],
-        argvalues: Iterable[Union["ParameterSet", Sequence[object], object]],
+        argnames: str | Sequence[str],
+        argvalues: Iterable[ParameterSet | Sequence[object] | object],
         func,
         config: Config,
         nodeid: str,
-    ) -> Tuple[Union[List[str], Tuple[str, ...]], List["ParameterSet"]]:
+    ) -> tuple[Sequence[str], list[ParameterSet]]:
         argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues)
         parameters = cls._parse_parametrize_parameters(argvalues, force_tuple)
         del argvalues
@@ -190,33 +196,39 @@ def _for_parametrize(
             # parameter set with NOTSET values, with the "empty parameter set" mark applied to it.
             mark = get_empty_parameterset_mark(config, argnames, func)
             parameters.append(
-                ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None)
+                ParameterSet(
+                    values=(NOTSET,) * len(argnames), marks=[mark], id="NOTSET"
+                )
             )
         return argnames, parameters
 
 
 @final
-@attr.s(frozen=True, init=False, auto_attribs=True)
+@dataclasses.dataclass(frozen=True)
 class Mark:
+    """A pytest mark."""
+
     #: Name of the mark.
     name: str
     #: Positional arguments of the mark decorator.
-    args: Tuple[Any, ...]
+    args: tuple[Any, ...]
     #: Keyword arguments of the mark decorator.
     kwargs: Mapping[str, Any]
 
     #: Source Mark for ids with parametrize Marks.
-    _param_ids_from: Optional["Mark"] = attr.ib(default=None, repr=False)
+    _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False)
     #: Resolved/generated ids with parametrize Marks.
-    _param_ids_generated: Optional[Sequence[str]] = attr.ib(default=None, repr=False)
+    _param_ids_generated: Sequence[str] | None = dataclasses.field(
+        default=None, repr=False
+    )
 
     def __init__(
         self,
         name: str,
-        args: Tuple[Any, ...],
+        args: tuple[Any, ...],
         kwargs: Mapping[str, Any],
-        param_ids_from: Optional["Mark"] = None,
-        param_ids_generated: Optional[Sequence[str]] = None,
+        param_ids_from: Mark | None = None,
+        param_ids_generated: Sequence[str] | None = None,
         *,
         _ispytest: bool = False,
     ) -> None:
@@ -232,7 +244,7 @@ def __init__(
     def _has_param_ids(self) -> bool:
         return "ids" in self.kwargs or len(self.args) >= 4
 
-    def combined_with(self, other: "Mark") -> "Mark":
+    def combined_with(self, other: Mark) -> Mark:
         """Return a new Mark which is a combination of this
         Mark and another Mark.
 
@@ -244,7 +256,7 @@ def combined_with(self, other: "Mark") -> "Mark":
         assert self.name == other.name
 
         # Remember source of ids with parametrize Marks.
-        param_ids_from: Optional[Mark] = None
+        param_ids_from: Mark | None = None
         if self.name == "parametrize":
             if other._has_param_ids():
                 param_ids_from = other
@@ -266,14 +278,14 @@ def combined_with(self, other: "Mark") -> "Mark":
 Markable = TypeVar("Markable", bound=Union[Callable[..., object], type])
 
 
-@attr.s(init=False, auto_attribs=True)
+@dataclasses.dataclass
 class MarkDecorator:
     """A decorator for applying a mark on test functions and classes.
 
     ``MarkDecorators`` are created with ``pytest.mark``::
 
-        mark1 = pytest.mark.NAME              # Simple MarkDecorator
-        mark2 = pytest.mark.NAME(name1=value) # Parametrized MarkDecorator
+        mark1 = pytest.mark.NAME  # Simple MarkDecorator
+        mark2 = pytest.mark.NAME(name1=value)  # Parametrized MarkDecorator
 
     and can then be applied as decorators to test functions::
 
@@ -315,7 +327,7 @@ def name(self) -> str:
         return self.mark.name
 
     @property
-    def args(self) -> Tuple[Any, ...]:
+    def args(self) -> tuple[Any, ...]:
         """Alias for mark.args."""
         return self.mark.args
 
@@ -329,7 +341,7 @@ def markname(self) -> str:
         """:meta private:"""
         return self.name  # for backward-compat (2.4.1 had this attr)
 
-    def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator":
+    def with_args(self, *args: object, **kwargs: object) -> MarkDecorator:
         """Return a MarkDecorator with extra arguments added.
 
         Unlike calling the MarkDecorator, with_args() can be used even
@@ -342,11 +354,11 @@ def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator":
     # return type. Not much we can do about that. Thankfully mypy picks
     # the first match so it works out even if we break the rules.
     @overload
-    def __call__(self, arg: Markable) -> Markable:  # type: ignore[misc]
+    def __call__(self, arg: Markable) -> Markable:  # type: ignore[overload-overlap]
         pass
 
     @overload
-    def __call__(self, *args: object, **kwargs: object) -> "MarkDecorator":
+    def __call__(self, *args: object, **kwargs: object) -> MarkDecorator:
         pass
 
     def __call__(self, *args: object, **kwargs: object):
@@ -354,22 +366,52 @@ def __call__(self, *args: object, **kwargs: object):
         if args and not kwargs:
             func = args[0]
             is_class = inspect.isclass(func)
-            if len(args) == 1 and (istestfunc(func) or is_class):
-                store_mark(func, self.mark)
+            # For staticmethods/classmethods, the marks are eventually fetched from the
+            # function object, not the descriptor, so unwrap.
+            unwrapped_func = func
+            if isinstance(func, (staticmethod, classmethod)):
+                unwrapped_func = func.__func__
+            if len(args) == 1 and (istestfunc(unwrapped_func) or is_class):
+                store_mark(unwrapped_func, self.mark, stacklevel=3)
                 return func
         return self.with_args(*args, **kwargs)
 
 
-def get_unpacked_marks(obj: object) -> Iterable[Mark]:
-    """Obtain the unpacked marks that are stored on an object."""
-    mark_list = getattr(obj, "pytestmark", [])
-    if not isinstance(mark_list, list):
-        mark_list = [mark_list]
-    return normalize_mark_list(mark_list)
+def get_unpacked_marks(
+    obj: object | type,
+    *,
+    consider_mro: bool = True,
+) -> list[Mark]:
+    """Obtain the unpacked marks that are stored on an object.
+
+    If obj is a class and consider_mro is true, return marks applied to
+    this class and all of its super-classes in MRO order. If consider_mro
+    is false, only return marks applied directly to this class.
+    """
+    if isinstance(obj, type):
+        if not consider_mro:
+            mark_lists = [obj.__dict__.get("pytestmark", [])]
+        else:
+            mark_lists = [
+                x.__dict__.get("pytestmark", []) for x in reversed(obj.__mro__)
+            ]
+        mark_list = []
+        for item in mark_lists:
+            if isinstance(item, list):
+                mark_list.extend(item)
+            else:
+                mark_list.append(item)
+    else:
+        mark_attribute = getattr(obj, "pytestmark", [])
+        if isinstance(mark_attribute, list):
+            mark_list = mark_attribute
+        else:
+            mark_list = [mark_attribute]
+    return list(normalize_mark_list(mark_list))
 
 
 def normalize_mark_list(
-    mark_list: Iterable[Union[Mark, MarkDecorator]]
+    mark_list: Iterable[Mark | MarkDecorator],
 ) -> Iterable[Mark]:
     """
     Normalize an iterable of Mark or MarkDecorator objects into a list of marks
@@ -381,77 +423,76 @@ def normalize_mark_list(
     for mark in mark_list:
         mark_obj = getattr(mark, "mark", mark)
         if not isinstance(mark_obj, Mark):
-            raise TypeError(f"got {repr(mark_obj)} instead of Mark")
+            raise TypeError(f"got {mark_obj!r} instead of Mark")
         yield mark_obj
 
 
-def store_mark(obj, mark: Mark) -> None:
+def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None:
     """Store a Mark on an object.
 
     This is used to implement the Mark declarations/decorators correctly.
     """
     assert isinstance(mark, Mark), mark
+
+    from ..fixtures import getfixturemarker
+
+    if getfixturemarker(obj) is not None:
+        warnings.warn(MARKED_FIXTURE, stacklevel=stacklevel)
+
     # Always reassign name to avoid updating pytestmark in a reference that
     # was only borrowed.
-    obj.pytestmark = [*get_unpacked_marks(obj), mark]
+    obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark]
 
 
 # Typing for builtin pytest marks. This is cheating; it gives builtin marks
 # special privilege, and breaks modularity. But practicality beats purity...
 if TYPE_CHECKING:
-    from _pytest.scope import _ScopeName
 
     class _SkipMarkDecorator(MarkDecorator):
-        @overload  # type: ignore[override,misc]
-        def __call__(self, arg: Markable) -> Markable:
-            ...
+        @overload  # type: ignore[override,no-overload-impl]
+        def __call__(self, arg: Markable) -> Markable: ...
 
         @overload
-        def __call__(self, reason: str = ...) -> "MarkDecorator":
-            ...
+        def __call__(self, reason: str = ...) -> MarkDecorator: ...
 
     class _SkipifMarkDecorator(MarkDecorator):
         def __call__(  # type: ignore[override]
             self,
-            condition: Union[str, bool] = ...,
-            *conditions: Union[str, bool],
+            condition: str | bool = ...,
+            *conditions: str | bool,
             reason: str = ...,
-        ) -> MarkDecorator:
-            ...
+        ) -> MarkDecorator: ...
 
     class _XfailMarkDecorator(MarkDecorator):
-        @overload  # type: ignore[override,misc]
-        def __call__(self, arg: Markable) -> Markable:
-            ...
+        @overload  # type: ignore[override,no-overload-impl]
+        def __call__(self, arg: Markable) -> Markable: ...
 
         @overload
         def __call__(
             self,
-            condition: Union[str, bool] = ...,
-            *conditions: Union[str, bool],
+            condition: str | bool = False,
+            *conditions: str | bool,
             reason: str = ...,
             run: bool = ...,
-            raises: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = ...,
+            raises: None
+            | type[BaseException]
+            | tuple[type[BaseException], ...]
+            | AbstractRaises[BaseException] = ...,
             strict: bool = ...,
-        ) -> MarkDecorator:
-            ...
+        ) -> MarkDecorator: ...
 
     class _ParametrizeMarkDecorator(MarkDecorator):
         def __call__(  # type: ignore[override]
             self,
-            argnames: Union[str, List[str], Tuple[str, ...]],
-            argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
+            argnames: str | Sequence[str],
+            argvalues: Iterable[ParameterSet | Sequence[object] | object],
             *,
-            indirect: Union[bool, Sequence[str]] = ...,
-            ids: Optional[
-                Union[
-                    Iterable[Union[None, str, float, int, bool]],
-                    Callable[[Any], Optional[object]],
-                ]
-            ] = ...,
-            scope: Optional[_ScopeName] = ...,
-        ) -> MarkDecorator:
-            ...
+            indirect: bool | Sequence[str] = ...,
+            ids: Iterable[None | str | float | int | bool]
+            | Callable[[Any], object | None]
+            | None = ...,
+            scope: _ScopeName | None = ...,
+        ) -> MarkDecorator: ...
 
     class _UsefixturesMarkDecorator(MarkDecorator):
         def __call__(self, *fixtures: str) -> MarkDecorator:  # type: ignore[override]
@@ -471,9 +512,10 @@ class MarkGenerator:
 
          import pytest
 
+
          @pytest.mark.slowtest
          def test_function():
-            pass
+             pass
 
     applies a 'slowtest' :class:`Mark` on ``test_function``.
     """
@@ -489,8 +531,8 @@ def test_function():
 
     def __init__(self, *, _ispytest: bool = False) -> None:
         check_ispytest(_ispytest)
-        self._config: Optional[Config] = None
-        self._markers: Set[str] = set()
+        self._config: Config | None = None
+        self._markers: set[str] = set()
 
     def __getattr__(self, name: str) -> MarkDecorator:
         """Generate a new :class:`MarkDecorator` with the given name."""
@@ -524,9 +566,9 @@ def __getattr__(self, name: str) -> MarkDecorator:
                     fail(f"Unknown '{name}' mark, did you mean 'parametrize'?")
 
                 warnings.warn(
-                    "Unknown pytest.mark.%s - is this a typo?  You can register "
+                    f"Unknown pytest.mark.{name} - is this a typo?  You can register "
                     "custom marks to avoid this warning - for details, see "
-                    "https://docs.pytest.org/en/stable/how-to/mark.html" % name,
+                    "https://docs.pytest.org/en/stable/how-to/mark.html",
                     PytestUnknownMarkWarning,
                     2,
                 )
@@ -539,9 +581,9 @@ def __getattr__(self, name: str) -> MarkDecorator:
 
 @final
 class NodeKeywords(MutableMapping[str, Any]):
-    __slots__ = ("node", "parent", "_markers")
+    __slots__ = ("_markers", "node", "parent")
 
-    def __init__(self, node: "Node") -> None:
+    def __init__(self, node: Node) -> None:
         self.node = node
         self.parent = node.parent
         self._markers = {node.name: True}
@@ -561,15 +603,13 @@ def __setitem__(self, key: str, value: Any) -> None:
     # below and use the collections.abc fallback, but that would be slow.
 
     def __contains__(self, key: object) -> bool:
-        return (
-            key in self._markers
-            or self.parent is not None
-            and key in self.parent.keywords
+        return key in self._markers or (
+            self.parent is not None and key in self.parent.keywords
         )
 
     def update(  # type: ignore[override]
         self,
-        other: Union[Mapping[str, Any], Iterable[Tuple[str, Any]]] = (),
+        other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (),
         **kwds: Any,
     ) -> None:
         self._markers.update(other)
diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py
index 31f95a95ab2..1285e571551 100644
--- a/src/_pytest/monkeypatch.py
+++ b/src/_pytest/monkeypatch.py
@@ -1,23 +1,25 @@
+# mypy: allow-untyped-defs
 """Monkeypatching and mocking functionality."""
+
+from __future__ import annotations
+
+from collections.abc import Generator
+from collections.abc import Mapping
+from collections.abc import MutableMapping
+from contextlib import contextmanager
 import os
 import re
 import sys
-import warnings
-from contextlib import contextmanager
 from typing import Any
-from typing import Generator
-from typing import List
-from typing import MutableMapping
-from typing import Optional
+from typing import final
 from typing import overload
-from typing import Tuple
 from typing import TypeVar
-from typing import Union
+import warnings
 
-from _pytest.compat import final
 from _pytest.fixtures import fixture
 from _pytest.warning_types import PytestWarning
 
+
 RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$")
 
 
@@ -26,24 +28,29 @@
 
 
 @fixture
-def monkeypatch() -> Generator["MonkeyPatch", None, None]:
+def monkeypatch() -> Generator[MonkeyPatch]:
     """A convenient fixture for monkey-patching.
 
-    The fixture provides these methods to modify objects, dictionaries or
-    os.environ::
+    The fixture provides these methods to modify objects, dictionaries, or
+    :data:`os.environ`:
 
-        monkeypatch.setattr(obj, name, value, raising=True)
-        monkeypatch.delattr(obj, name, raising=True)
-        monkeypatch.setitem(mapping, name, value)
-        monkeypatch.delitem(obj, name, raising=True)
-        monkeypatch.setenv(name, value, prepend=None)
-        monkeypatch.delenv(name, raising=True)
-        monkeypatch.syspath_prepend(path)
-        monkeypatch.chdir(path)
+    * :meth:`monkeypatch.setattr(obj, name, value, raising=True) <pytest.MonkeyPatch.setattr>`
+    * :meth:`monkeypatch.delattr(obj, name, raising=True) <pytest.MonkeyPatch.delattr>`
+    * :meth:`monkeypatch.setitem(mapping, name, value) <pytest.MonkeyPatch.setitem>`
+    * :meth:`monkeypatch.delitem(obj, name, raising=True) <pytest.MonkeyPatch.delitem>`
+    * :meth:`monkeypatch.setenv(name, value, prepend=None) <pytest.MonkeyPatch.setenv>`
+    * :meth:`monkeypatch.delenv(name, raising=True) <pytest.MonkeyPatch.delenv>`
+    * :meth:`monkeypatch.syspath_prepend(path) <pytest.MonkeyPatch.syspath_prepend>`
+    * :meth:`monkeypatch.chdir(path) <pytest.MonkeyPatch.chdir>`
+    * :meth:`monkeypatch.context() <pytest.MonkeyPatch.context>`
 
     All modifications will be undone after the requesting test function or
-    fixture has finished. The ``raising`` parameter determines if a KeyError
-    or AttributeError will be raised if the set/deletion operation has no target.
+    fixture has finished. The ``raising`` parameter determines if a :class:`KeyError`
+    or :class:`AttributeError` will be raised if the set/deletion operation does not have the
+    specified target.
+
+    To undo modifications done by the fixture in a contained scope,
+    use :meth:`context() <pytest.MonkeyPatch.context>`.
     """
     mpatch = MonkeyPatch()
     yield mpatch
@@ -55,7 +62,7 @@ def resolve(name: str) -> object:
     parts = name.split(".")
 
     used = parts.pop(0)
-    found = __import__(used)
+    found: object = __import__(used)
     for part in parts:
         used += "." + part
         try:
@@ -83,14 +90,12 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object:
         obj = getattr(obj, name)
     except AttributeError as e:
         raise AttributeError(
-            "{!r} object at {} has no attribute {!r}".format(
-                type(obj).__name__, ann, name
-            )
+            f"{type(obj).__name__!r} object at {ann} has no attribute {name!r}"
         ) from e
     return obj
 
 
-def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]:
+def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]:
     if not isinstance(import_path, str) or "." not in import_path:
         raise TypeError(f"must be absolute import path string, not {import_path!r}")
     module, attr = import_path.rsplit(".", 1)
@@ -115,7 +120,7 @@ class MonkeyPatch:
 
     Returned by the :fixture:`monkeypatch` fixture.
 
-    :versionchanged:: 6.2
+    .. versionchanged:: 6.2
         Can now also be used directly as `pytest.MonkeyPatch()`, for when
         the fixture is not available. In this case, use
         :meth:`with MonkeyPatch.context() as mp: <context>` or remember to call
@@ -123,14 +128,14 @@ class MonkeyPatch:
     """
 
     def __init__(self) -> None:
-        self._setattr: List[Tuple[object, str, object]] = []
-        self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = []
-        self._cwd: Optional[str] = None
-        self._savesyspath: Optional[List[str]] = None
+        self._setattr: list[tuple[object, str, object]] = []
+        self._setitem: list[tuple[Mapping[Any, Any], object, object]] = []
+        self._cwd: str | None = None
+        self._savesyspath: list[str] | None = None
 
     @classmethod
     @contextmanager
-    def context(cls) -> Generator["MonkeyPatch", None, None]:
+    def context(cls) -> Generator[MonkeyPatch]:
         """Context manager that returns a new :class:`MonkeyPatch` object
         which undoes any patching done inside the ``with`` block upon exit.
 
@@ -162,8 +167,7 @@ def setattr(
         name: object,
         value: Notset = ...,
         raising: bool = ...,
-    ) -> None:
-        ...
+    ) -> None: ...
 
     @overload
     def setattr(
@@ -172,26 +176,49 @@ def setattr(
         name: str,
         value: object,
         raising: bool = ...,
-    ) -> None:
-        ...
+    ) -> None: ...
 
     def setattr(
         self,
-        target: Union[str, object],
-        name: Union[object, str],
+        target: str | object,
+        name: object | str,
         value: object = notset,
         raising: bool = True,
     ) -> None:
-        """Set attribute value on target, memorizing the old value.
+        """
+        Set attribute value on target, memorizing the old value.
+
+        For example:
 
-        For convenience you can specify a string as ``target`` which
+        .. code-block:: python
+
+            import os
+
+            monkeypatch.setattr(os, "getcwd", lambda: "/")
+
+        The code above replaces the :func:`os.getcwd` function by a ``lambda`` which
+        always returns ``"/"``.
+
+        For convenience, you can specify a string as ``target`` which
         will be interpreted as a dotted import path, with the last part
-        being the attribute name. For example,
-        ``monkeypatch.setattr("os.getcwd", lambda: "/")``
-        would set the ``getcwd`` function of the ``os`` module.
+        being the attribute name:
+
+        .. code-block:: python
 
-        Raises AttributeError if the attribute does not exist, unless
+            monkeypatch.setattr("os.getcwd", lambda: "/")
+
+        Raises :class:`AttributeError` if the attribute does not exist, unless
         ``raising`` is set to False.
+
+        **Where to patch**
+
+        ``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one.
+        There can be many names pointing to any individual object, so for patching to work you must ensure
+        that you patch the name used by the system under test.
+
+        See the section :ref:`Where to patch <python:where-to-patch>` in the :mod:`unittest.mock`
+        docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but
+        applies to ``monkeypatch.setattr`` as well.
         """
         __tracebackhide__ = True
         import inspect
@@ -225,8 +252,8 @@ def setattr(
 
     def delattr(
         self,
-        target: Union[object, str],
-        name: Union[str, Notset] = notset,
+        target: object | str,
+        name: str | Notset = notset,
         raising: bool = True,
     ) -> None:
         """Delete attribute ``name`` from ``target``.
@@ -261,12 +288,13 @@ def delattr(
             self._setattr.append((target, name, oldval))
             delattr(target, name)
 
-    def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None:
+    def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None:
         """Set dictionary entry ``name`` to value."""
         self._setitem.append((dic, name, dic.get(name, notset)))
-        dic[name] = value
+        # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
+        dic[name] = value  # type: ignore[index]
 
-    def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> None:
+    def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None:
         """Delete ``name`` from dict.
 
         Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to
@@ -277,9 +305,10 @@ def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> N
                 raise KeyError(name)
         else:
             self._setitem.append((dic, name, dic.get(name, notset)))
-            del dic[name]
+            # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
+            del dic[name]  # type: ignore[attr-defined]
 
-    def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None:
+    def setenv(self, name: str, value: str, prepend: str | None = None) -> None:
         """Set environment variable ``name`` to ``value``.
 
         If ``prepend`` is a character, read the current environment variable
@@ -289,10 +318,8 @@ def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None:
         if not isinstance(value, str):
             warnings.warn(  # type: ignore[unreachable]
                 PytestWarning(
-                    "Value of environment variable {name} type should be str, but got "
-                    "{value!r} (type: {type}); converted to str implicitly".format(
-                        name=name, value=value, type=type(value).__name__
-                    )
+                    f"Value of environment variable {name} type should be str, but got "
+                    f"{value!r} (type: {type(value).__name__}); converted to str implicitly"
                 ),
                 stacklevel=2,
             )
@@ -312,7 +339,6 @@ def delenv(self, name: str, raising: bool = True) -> None:
 
     def syspath_prepend(self, path) -> None:
         """Prepend ``path`` to ``sys.path`` list of import locations."""
-
         if self._savesyspath is None:
             self._savesyspath = sys.path[:]
         sys.path.insert(0, str(path))
@@ -335,10 +361,11 @@ def syspath_prepend(self, path) -> None:
 
         invalidate_caches()
 
-    def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None:
+    def chdir(self, path: str | os.PathLike[str]) -> None:
         """Change the current working directory to the specified path.
 
-        Path can be a string or a path object.
+        :param path:
+            The path to change into.
         """
         if self._cwd is None:
             self._cwd = os.getcwd()
@@ -353,11 +380,14 @@ def undo(self) -> None:
         There is generally no need to call `undo()`, since it is
         called automatically during tear-down.
 
-        Note that the same `monkeypatch` fixture is used across a
-        single test function invocation. If `monkeypatch` is used both by
-        the test function itself and one of the test fixtures,
-        calling `undo()` will undo all of the changes made in
-        both functions.
+        .. note::
+            The same `monkeypatch` fixture is used across a
+            single test function invocation. If `monkeypatch` is used both by
+            the test function itself and one of the test fixtures,
+            calling `undo()` will undo all of the changes made in
+            both functions.
+
+            Prefer to use :meth:`context() <pytest.MonkeyPatch.context>` instead.
         """
         for obj, name, value in reversed(self._setattr):
             if value is not notset:
@@ -368,11 +398,13 @@ def undo(self) -> None:
         for dictionary, key, value in reversed(self._setitem):
             if value is notset:
                 try:
-                    del dictionary[key]
+                    # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
+                    del dictionary[key]  # type: ignore[attr-defined]
                 except KeyError:
                     pass  # Was already deleted, so we have the desired state.
             else:
-                dictionary[key] = value
+                # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
+                dictionary[key] = value  # type: ignore[index]
         self._setitem[:] = []
         if self._savesyspath is not None:
             sys.path[:] = self._savesyspath
diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py
index 53dea04e795..0d1f3d2352b 100644
--- a/src/_pytest/nodes.py
+++ b/src/_pytest/nodes.py
@@ -1,32 +1,36 @@
-import os
-import warnings
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+import abc
+from collections.abc import Callable
+from collections.abc import Iterable
+from collections.abc import Iterator
+from collections.abc import MutableMapping
+from functools import cached_property
 from inspect import signature
+import os
+import pathlib
 from pathlib import Path
 from typing import Any
-from typing import Callable
 from typing import cast
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import MutableMapping
-from typing import Optional
+from typing import NoReturn
 from typing import overload
-from typing import Set
-from typing import Tuple
-from typing import Type
 from typing import TYPE_CHECKING
 from typing import TypeVar
-from typing import Union
+import warnings
+
+import pluggy
 
 import _pytest._code
 from _pytest._code import getfslineno
 from _pytest._code.code import ExceptionInfo
 from _pytest._code.code import TerminalRepr
-from _pytest.compat import cached_property
+from _pytest._code.code import Traceback
+from _pytest._code.code import TracebackStyle
 from _pytest.compat import LEGACY_PATH
 from _pytest.config import Config
 from _pytest.config import ConftestImportFailure
-from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
+from _pytest.config.compat import _check_path
 from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
 from _pytest.mark.structures import Mark
 from _pytest.mark.structures import MarkDecorator
@@ -37,10 +41,12 @@
 from _pytest.stash import Stash
 from _pytest.warning_types import PytestWarning
 
+
 if TYPE_CHECKING:
+    from typing_extensions import Self
+
     # Imported here due to circular import.
     from _pytest.main import Session
-    from _pytest._code.code import _TracebackStyle
 
 
 SEP = "/"
@@ -48,70 +54,20 @@
 tracebackcutdir = Path(_pytest.__file__).parent
 
 
-def iterparentnodeids(nodeid: str) -> Iterator[str]:
-    """Return the parent node IDs of a given node ID, inclusive.
-
-    For the node ID
-
-        "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"
-
-    the result would be
-
-        ""
-        "testing"
-        "testing/code"
-        "testing/code/test_excinfo.py"
-        "testing/code/test_excinfo.py::TestFormattedExcinfo"
-        "testing/code/test_excinfo.py::TestFormattedExcinfo::test_repr_source"
-
-    Note that / components are only considered until the first ::.
-    """
-    pos = 0
-    first_colons: Optional[int] = nodeid.find("::")
-    if first_colons == -1:
-        first_colons = None
-    # The root Session node - always present.
-    yield ""
-    # Eagerly consume SEP parts until first colons.
-    while True:
-        at = nodeid.find(SEP, pos, first_colons)
-        if at == -1:
-            break
-        if at > 0:
-            yield nodeid[:at]
-        pos = at + len(SEP)
-    # Eagerly consume :: parts.
-    while True:
-        at = nodeid.find("::", pos)
-        if at == -1:
-            break
-        if at > 0:
-            yield nodeid[:at]
-        pos = at + len("::")
-    # The node ID itself.
-    if nodeid:
-        yield nodeid
-
-
-def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
-    if Path(fspath) != path:
-        raise ValueError(
-            f"Path({fspath!r}) != {path!r}\n"
-            "if both path and fspath are given they need to be equal"
-        )
+_T = TypeVar("_T")
 
 
 def _imply_path(
-    node_type: Type["Node"],
-    path: Optional[Path],
-    fspath: Optional[LEGACY_PATH],
+    node_type: type[Node],
+    path: Path | None,
+    fspath: LEGACY_PATH | None,
 ) -> Path:
     if fspath is not None:
         warnings.warn(
             NODE_CTOR_FSPATH_ARG.format(
                 node_type_name=node_type.__name__,
             ),
-            stacklevel=3,
+            stacklevel=6,
         )
     if path is not None:
         if fspath is not None:
@@ -125,72 +81,90 @@ def _imply_path(
 _NodeType = TypeVar("_NodeType", bound="Node")
 
 
-class NodeMeta(type):
-    def __call__(self, *k, **kw):
+class NodeMeta(abc.ABCMeta):
+    """Metaclass used by :class:`Node` to enforce that direct construction raises
+    :class:`Failed`.
+
+    This behaviour supports the indirection introduced with :meth:`Node.from_parent`,
+    the named constructor to be used instead of direct construction. The design
+    decision to enforce indirection with :class:`NodeMeta` was made as a
+    temporary aid for refactoring the collection tree, which was diagnosed to
+    have :class:`Node` objects whose creational patterns were overly entangled.
+    Once the refactoring is complete, this metaclass can be removed.
+
+    See https://github.com/pytest-dev/pytest/projects/3 for an overview of the
+    progress on detangling the :class:`Node` classes.
+    """
+
+    def __call__(cls, *k, **kw) -> NoReturn:
         msg = (
             "Direct construction of {name} has been deprecated, please use {name}.from_parent.\n"
             "See "
             "https://docs.pytest.org/en/stable/deprecations.html#node-construction-changed-to-node-from-parent"
             " for more details."
-        ).format(name=f"{self.__module__}.{self.__name__}")
+        ).format(name=f"{cls.__module__}.{cls.__name__}")
         fail(msg, pytrace=False)
 
-    def _create(self, *k, **kw):
+    def _create(cls: type[_T], *k, **kw) -> _T:
         try:
-            return super().__call__(*k, **kw)
+            return super().__call__(*k, **kw)  # type: ignore[no-any-return,misc]
         except TypeError:
-            sig = signature(getattr(self, "__init__"))
+            sig = signature(getattr(cls, "__init__"))
             known_kw = {k: v for k, v in kw.items() if k in sig.parameters}
             from .warning_types import PytestDeprecationWarning
 
             warnings.warn(
                 PytestDeprecationWarning(
-                    f"{self} is not using a cooperative constructor and only takes {set(known_kw)}"
+                    f"{cls} is not using a cooperative constructor and only takes {set(known_kw)}.\n"
+                    "See https://docs.pytest.org/en/stable/deprecations.html"
+                    "#constructors-of-custom-pytest-node-subclasses-should-take-kwargs "
+                    "for more details."
                 )
             )
 
-            return super().__call__(*k, **known_kw)
+            return super().__call__(*k, **known_kw)  # type: ignore[no-any-return,misc]
 
 
-class Node(metaclass=NodeMeta):
-    """Base class for Collector and Item, the components of the test
-    collection tree.
+class Node(abc.ABC, metaclass=NodeMeta):
+    r"""Base class of :class:`Collector` and :class:`Item`, the components of
+    the test collection tree.
 
-    Collector subclasses have children; Items are leaf nodes.
+    ``Collector``\'s are the internal nodes of the tree, and ``Item``\'s are the
+    leaf nodes.
     """
 
     # Implemented in the legacypath plugin.
     #: A ``LEGACY_PATH`` copy of the :attr:`path` attribute. Intended for usage
     #: for methods not migrated to ``pathlib.Path`` yet, such as
-    #: :meth:`Item.reportinfo`. Will be deprecated in a future release, prefer
-    #: using :attr:`path` instead.
+    #: :meth:`Item.reportinfo <pytest.Item.reportinfo>`. Will be deprecated in
+    #: a future release, prefer using :attr:`path` instead.
     fspath: LEGACY_PATH
 
     # Use __slots__ to make attribute access faster.
     # Note that __dict__ is still available.
     __slots__ = (
+        "__dict__",
+        "_nodeid",
+        "_store",
+        "config",
         "name",
         "parent",
-        "config",
-        "session",
         "path",
-        "_nodeid",
-        "_store",
-        "__dict__",
+        "session",
     )
 
     def __init__(
         self,
         name: str,
-        parent: "Optional[Node]" = None,
-        config: Optional[Config] = None,
-        session: "Optional[Session]" = None,
-        fspath: Optional[LEGACY_PATH] = None,
-        path: Optional[Path] = None,
-        nodeid: Optional[str] = None,
+        parent: Node | None = None,
+        config: Config | None = None,
+        session: Session | None = None,
+        fspath: LEGACY_PATH | None = None,
+        path: Path | None = None,
+        nodeid: str | None = None,
     ) -> None:
         #: A unique name within the scope of the parent node.
-        self.name = name
+        self.name: str = name
 
         #: The parent collector node.
         self.parent = parent
@@ -205,7 +179,7 @@ def __init__(
 
         if session:
             #: The pytest session this node is part of.
-            self.session = session
+            self.session: Session = session
         else:
             if not parent:
                 raise TypeError("session or parent must be provided")
@@ -214,17 +188,17 @@ def __init__(
         if path is None and fspath is None:
             path = getattr(parent, "path", None)
         #: Filesystem path where this node was collected from (can be None).
-        self.path: Path = _imply_path(type(self), path, fspath=fspath)
+        self.path: pathlib.Path = _imply_path(type(self), path, fspath=fspath)
 
         # The explicit annotation is to avoid publicly exposing NodeKeywords.
         #: Keywords/markers collected from all scopes.
         self.keywords: MutableMapping[str, Any] = NodeKeywords(self)
 
         #: The marker objects belonging to this node.
-        self.own_markers: List[Mark] = []
+        self.own_markers: list[Mark] = []
 
         #: Allow adding of extra keywords to use for matching.
-        self.extra_keyword_matches: Set[str] = set()
+        self.extra_keyword_matches: set[str] = set()
 
         if nodeid is not None:
             assert "::()" not in nodeid
@@ -236,14 +210,12 @@ def __init__(
 
         #: A place where plugins can store information on the node for their
         #: own use.
-        #:
-        #: :type: Stash
-        self.stash = Stash()
+        self.stash: Stash = Stash()
         # Deprecated alias. Was never public. Can be removed in a few releases.
         self._store = self.stash
 
     @classmethod
-    def from_parent(cls, parent: "Node", **kw):
+    def from_parent(cls, parent: Node, **kw) -> Self:
         """Public constructor for Nodes.
 
         This indirection got introduced in order to enable removing
@@ -261,7 +233,7 @@ def from_parent(cls, parent: "Node", **kw):
         return cls._create(parent=parent, **kw)
 
     @property
-    def ihook(self):
+    def ihook(self) -> pluggy.HookRelay:
         """fspath-sensitive hook proxy used to call pytest hooks."""
         return self.session.gethookproxy(self.path)
 
@@ -292,9 +264,7 @@ def warn(self, warning: Warning) -> None:
         # enforce type checks here to avoid getting a generic type error later otherwise.
         if not isinstance(warning, Warning):
             raise ValueError(
-                "warning must be an instance of Warning or subclass, got {!r}".format(
-                    warning
-                )
+                f"warning must be an instance of Warning or subclass, got {warning!r}"
             )
         path, lineno = get_fslocation_from_item(self)
         assert lineno is not None
@@ -321,22 +291,33 @@ def setup(self) -> None:
     def teardown(self) -> None:
         pass
 
-    def listchain(self) -> List["Node"]:
-        """Return list of all parent collectors up to self, starting from
-        the root of collection tree."""
+    def iter_parents(self) -> Iterator[Node]:
+        """Iterate over all parent collectors starting from and including self
+        up to the root of the collection tree.
+
+        .. versionadded:: 8.1
+        """
+        parent: Node | None = self
+        while parent is not None:
+            yield parent
+            parent = parent.parent
+
+    def listchain(self) -> list[Node]:
+        """Return a list of all parent collectors starting from the root of the
+        collection tree down to and including self."""
         chain = []
-        item: Optional[Node] = self
+        item: Node | None = self
         while item is not None:
             chain.append(item)
             item = item.parent
         chain.reverse()
         return chain
 
-    def add_marker(
-        self, marker: Union[str, MarkDecorator], append: bool = True
-    ) -> None:
+    def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None:
         """Dynamically add a marker object to the node.
 
+        :param marker:
+            The marker.
         :param append:
             Whether to append the marker, or prepend it.
         """
@@ -354,37 +335,34 @@ def add_marker(
         else:
             self.own_markers.insert(0, marker_.mark)
 
-    def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]:
+    def iter_markers(self, name: str | None = None) -> Iterator[Mark]:
         """Iterate over all markers of the node.
 
         :param name: If given, filter the results by the name attribute.
+        :returns: An iterator of the markers of the node.
         """
         return (x[1] for x in self.iter_markers_with_node(name=name))
 
     def iter_markers_with_node(
-        self, name: Optional[str] = None
-    ) -> Iterator[Tuple["Node", Mark]]:
+        self, name: str | None = None
+    ) -> Iterator[tuple[Node, Mark]]:
         """Iterate over all markers of the node.
 
         :param name: If given, filter the results by the name attribute.
         :returns: An iterator of (node, mark) tuples.
         """
-        for node in reversed(self.listchain()):
+        for node in self.iter_parents():
             for mark in node.own_markers:
                 if name is None or getattr(mark, "name", None) == name:
                     yield node, mark
 
     @overload
-    def get_closest_marker(self, name: str) -> Optional[Mark]:
-        ...
+    def get_closest_marker(self, name: str) -> Mark | None: ...
 
     @overload
-    def get_closest_marker(self, name: str, default: Mark) -> Mark:
-        ...
+    def get_closest_marker(self, name: str, default: Mark) -> Mark: ...
 
-    def get_closest_marker(
-        self, name: str, default: Optional[Mark] = None
-    ) -> Optional[Mark]:
+    def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None:
         """Return the first marker matching the name, from closest (for
         example function) to farther level (for example module level).
 
@@ -393,57 +371,61 @@ def get_closest_marker(
         """
         return next(self.iter_markers(name=name), default)
 
-    def listextrakeywords(self) -> Set[str]:
+    def listextrakeywords(self) -> set[str]:
         """Return a set of all extra keywords in self and any parents."""
-        extra_keywords: Set[str] = set()
+        extra_keywords: set[str] = set()
         for item in self.listchain():
             extra_keywords.update(item.extra_keyword_matches)
         return extra_keywords
 
-    def listnames(self) -> List[str]:
+    def listnames(self) -> list[str]:
         return [x.name for x in self.listchain()]
 
     def addfinalizer(self, fin: Callable[[], object]) -> None:
-        """Register a function to be called when this node is finalized.
+        """Register a function to be called without arguments when this node is
+        finalized.
 
         This method can only be called when this node is active
         in a setup chain, for example during self.setup().
         """
         self.session._setupstate.addfinalizer(fin, self)
 
-    def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]:
-        """Get the next parent node (including self) which is an instance of
-        the given class."""
-        current: Optional[Node] = self
-        while current and not isinstance(current, cls):
-            current = current.parent
-        assert current is None or isinstance(current, cls)
-        return current
+    def getparent(self, cls: type[_NodeType]) -> _NodeType | None:
+        """Get the closest parent node (including self) which is an instance of
+        the given class.
 
-    def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
-        pass
+        :param cls: The node class to search for.
+        :returns: The node, if found.
+        """
+        for node in self.iter_parents():
+            if isinstance(node, cls):
+                return node
+        return None
+
+    def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
+        return excinfo.traceback
 
     def _repr_failure_py(
         self,
         excinfo: ExceptionInfo[BaseException],
-        style: "Optional[_TracebackStyle]" = None,
+        style: TracebackStyle | None = None,
     ) -> TerminalRepr:
         from _pytest.fixtures import FixtureLookupError
 
         if isinstance(excinfo.value, ConftestImportFailure):
-            excinfo = ExceptionInfo.from_exc_info(excinfo.value.excinfo)
+            excinfo = ExceptionInfo.from_exception(excinfo.value.cause)
         if isinstance(excinfo.value, fail.Exception):
             if not excinfo.value.pytrace:
                 style = "value"
         if isinstance(excinfo.value, FixtureLookupError):
             return excinfo.value.formatrepr()
+
+        tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback]
         if self.config.getoption("fulltrace", False):
             style = "long"
+            tbfilter = False
         else:
-            tb = _pytest._code.Traceback([excinfo.traceback[-1]])
-            self._prunetraceback(excinfo)
-            if len(excinfo.traceback) == 0:
-                excinfo.traceback = tb
+            tbfilter = self._traceback_filter
             if style == "auto":
                 style = "long"
         # XXX should excinfo.getrepr record all data and toterminal() process it?
@@ -453,11 +435,13 @@ def _repr_failure_py(
             else:
                 style = "long"
 
-        if self.config.getoption("verbose", 0) > 1:
+        if self.config.get_verbosity() > 1:
             truncate_locals = False
         else:
             truncate_locals = True
 
+        truncate_args = False if self.config.get_verbosity() > 2 else True
+
         # excinfo.getrepr() formats paths relative to the CWD if `abspath` is False.
         # It is possible for a fixture/test to change the CWD while this code runs, which
         # would then result in the user seeing confusing paths in the failure message.
@@ -474,15 +458,16 @@ def _repr_failure_py(
             abspath=abspath,
             showlocals=self.config.getoption("showlocals", False),
             style=style,
-            tbfilter=False,  # pruned already, or in --fulltrace mode.
+            tbfilter=tbfilter,
             truncate_locals=truncate_locals,
+            truncate_args=truncate_args,
         )
 
     def repr_failure(
         self,
         excinfo: ExceptionInfo[BaseException],
-        style: "Optional[_TracebackStyle]" = None,
-    ) -> Union[str, TerminalRepr]:
+        style: TracebackStyle | None = None,
+    ) -> str | TerminalRepr:
         """Return a representation of a collection or test failure.
 
         .. seealso:: :ref:`non-python tests`
@@ -492,41 +477,44 @@ def repr_failure(
         return self._repr_failure_py(excinfo, style)
 
 
-def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]:
+def get_fslocation_from_item(node: Node) -> tuple[str | Path, int | None]:
     """Try to extract the actual location from a node, depending on available attributes:
 
     * "location": a pair (path, lineno)
     * "obj": a Python object that the node wraps.
-    * "fspath": just a path
+    * "path": just a path
 
-    :rtype: A tuple of (str|Path, int) with filename and line number.
+    :rtype: A tuple of (str|Path, int) with filename and 0-based line number.
     """
     # See Item.location.
-    location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None)
+    location: tuple[str, int | None, str] | None = getattr(node, "location", None)
     if location is not None:
         return location[:2]
     obj = getattr(node, "obj", None)
     if obj is not None:
         return getfslineno(obj)
-    return getattr(node, "fspath", "unknown location"), -1
+    return getattr(node, "path", "unknown location"), -1
+
 
+class Collector(Node, abc.ABC):
+    """Base class of all collectors.
 
-class Collector(Node):
-    """Collector instances create children through collect() and thus
-    iteratively build a tree."""
+    Collector create children through `collect()` and thus iteratively build
+    the collection tree.
+    """
 
     class CollectError(Exception):
         """An error during collection, contains a custom message."""
 
-    def collect(self) -> Iterable[Union["Item", "Collector"]]:
-        """Return a list of children (items and collectors) for this
-        collection node."""
+    @abc.abstractmethod
+    def collect(self) -> Iterable[Item | Collector]:
+        """Collect children (items and collectors) for this collector."""
         raise NotImplementedError("abstract")
 
     # TODO: This omits the style= parameter which breaks Liskov Substitution.
     def repr_failure(  # type: ignore[override]
         self, excinfo: ExceptionInfo[BaseException]
-    ) -> Union[str, TerminalRepr]:
+    ) -> str | TerminalRepr:
         """Return a representation of a collection failure.
 
         :param excinfo: Exception information for the failure.
@@ -545,16 +533,17 @@ def repr_failure(  # type: ignore[override]
 
         return self._repr_failure_py(excinfo, style=tbstyle)
 
-    def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
+    def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
         if hasattr(self, "path"):
             traceback = excinfo.traceback
             ntraceback = traceback.cut(path=self.path)
             if ntraceback == traceback:
                 ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
-            excinfo.traceback = ntraceback.filter()
+            return ntraceback.filter(excinfo)
+        return excinfo.traceback
 
 
-def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]:
+def _check_initialpaths_for_relpath(session: Session, path: Path) -> str | None:
     for initial_path in session._initialpaths:
         if commonpath(path, initial_path) == initial_path:
             rel = str(path.relative_to(initial_path))
@@ -562,17 +551,19 @@ def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[
     return None
 
 
-class FSCollector(Collector):
+class FSCollector(Collector, abc.ABC):
+    """Base class for filesystem collectors."""
+
     def __init__(
         self,
-        fspath: Optional[LEGACY_PATH] = None,
-        path_or_parent: Optional[Union[Path, Node]] = None,
-        path: Optional[Path] = None,
-        name: Optional[str] = None,
-        parent: Optional[Node] = None,
-        config: Optional[Config] = None,
-        session: Optional["Session"] = None,
-        nodeid: Optional[str] = None,
+        fspath: LEGACY_PATH | None = None,
+        path_or_parent: Path | Node | None = None,
+        path: Path | None = None,
+        name: str | None = None,
+        parent: Node | None = None,
+        config: Config | None = None,
+        session: Session | None = None,
+        nodeid: str | None = None,
     ) -> None:
         if path_or_parent:
             if isinstance(path_or_parent, Node):
@@ -622,58 +613,54 @@ def from_parent(
         cls,
         parent,
         *,
-        fspath: Optional[LEGACY_PATH] = None,
-        path: Optional[Path] = None,
+        fspath: LEGACY_PATH | None = None,
+        path: Path | None = None,
         **kw,
-    ):
+    ) -> Self:
         """The public constructor."""
         return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)
 
-    def gethookproxy(self, fspath: "os.PathLike[str]"):
-        warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
-        return self.session.gethookproxy(fspath)
-
-    def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool:
-        warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
-        return self.session.isinitpath(path)
 
-
-class File(FSCollector):
+class File(FSCollector, abc.ABC):
     """Base class for collecting tests from a file.
 
     :ref:`non-python tests`.
     """
 
 
-class Item(Node):
-    """A basic test invocation item.
+class Directory(FSCollector, abc.ABC):
+    """Base class for collecting files from a directory.
+
+    A basic directory collector does the following: goes over the files and
+    sub-directories in the directory and creates collectors for them by calling
+    the hooks :hook:`pytest_collect_directory` and :hook:`pytest_collect_file`,
+    after checking that they are not ignored using
+    :hook:`pytest_ignore_collect`.
+
+    The default directory collectors are :class:`~pytest.Dir` and
+    :class:`~pytest.Package`.
+
+    .. versionadded:: 8.0
+
+    :ref:`custom directory collectors`.
+    """
+
+
+class Item(Node, abc.ABC):
+    """Base class of all test invocation items.
 
     Note that for a single function there might be multiple test invocation items.
     """
 
     nextitem = None
 
-    def __init_subclass__(cls) -> None:
-        problems = ", ".join(
-            base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
-        )
-        if problems:
-            warnings.warn(
-                f"{cls.__name__} is an Item subclass and should not be a collector, "
-                f"however its bases {problems} are collectors.\n"
-                "Please split the Collectors and the Item into separate node types.\n"
-                "Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
-                "example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
-                PytestWarning,
-            )
-
     def __init__(
         self,
         name,
         parent=None,
-        config: Optional[Config] = None,
-        session: Optional["Session"] = None,
-        nodeid: Optional[str] = None,
+        config: Config | None = None,
+        session: Session | None = None,
+        nodeid: str | None = None,
         **kw,
     ) -> None:
         # The first two arguments are intentionally passed positionally,
@@ -688,12 +675,44 @@ def __init__(
             nodeid=nodeid,
             **kw,
         )
-        self._report_sections: List[Tuple[str, str, str]] = []
+        self._report_sections: list[tuple[str, str, str]] = []
 
         #: A list of tuples (name, value) that holds user defined properties
         #: for this test.
-        self.user_properties: List[Tuple[str, object]] = []
+        self.user_properties: list[tuple[str, object]] = []
 
+        self._check_item_and_collector_diamond_inheritance()
+
+    def _check_item_and_collector_diamond_inheritance(self) -> None:
+        """
+        Check if the current type inherits from both File and Collector
+        at the same time, emitting a warning accordingly (#8447).
+        """
+        cls = type(self)
+
+        # We inject an attribute in the type to avoid issuing this warning
+        # for the same class more than once, which is not helpful.
+        # It is a hack, but was deemed acceptable in order to avoid
+        # flooding the user in the common case.
+        attr_name = "_pytest_diamond_inheritance_warning_shown"
+        if getattr(cls, attr_name, False):
+            return
+        setattr(cls, attr_name, True)
+
+        problems = ", ".join(
+            base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
+        )
+        if problems:
+            warnings.warn(
+                f"{cls.__name__} is an Item subclass and should not be a collector, "
+                f"however its bases {problems} are collectors.\n"
+                "Please split the Collectors and the Item into separate node types.\n"
+                "Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
+                "example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
+                PytestWarning,
+            )
+
+    @abc.abstractmethod
     def runtest(self) -> None:
         """Run the test case for this item.
 
@@ -720,13 +739,13 @@ def add_report_section(self, when: str, key: str, content: str) -> None:
         if content:
             self._report_sections.append((when, key, content))
 
-    def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
+    def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
         """Get location information for this item for test reports.
 
         Returns a tuple with three elements:
 
         - The path of the test (default ``self.path``)
-        - The line number of the test (default ``None``)
+        - The 0-based line number of the test (default ``None``)
         - A name of the test to be shown (default ``""``)
 
         .. seealso:: :ref:`non-python tests`
@@ -734,9 +753,14 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str
         return self.path, None, ""
 
     @cached_property
-    def location(self) -> Tuple[str, Optional[int], str]:
+    def location(self) -> tuple[str, int | None, str]:
+        """
+        Returns a tuple of ``(relfspath, lineno, testname)`` for this item
+        where ``relfspath`` is file path relative to ``config.rootpath``
+        and lineno is a 0-based line number.
+        """
         location = self.reportinfo()
-        path = absolutepath(os.fspath(location[0]))
+        path = absolutepath(location[0])
         relfspath = self.session._node_location_to_relpath(path)
         assert type(location[2]) is str
         return (relfspath, location[1], location[2])
diff --git a/src/_pytest/nose.py b/src/_pytest/nose.py
deleted file mode 100644
index b0699d22bd8..00000000000
--- a/src/_pytest/nose.py
+++ /dev/null
@@ -1,42 +0,0 @@
-"""Run testsuites written for nose."""
-from _pytest.config import hookimpl
-from _pytest.fixtures import getfixturemarker
-from _pytest.nodes import Item
-from _pytest.python import Function
-from _pytest.unittest import TestCaseFunction
-
-
-@hookimpl(trylast=True)
-def pytest_runtest_setup(item: Item) -> None:
-    if not isinstance(item, Function):
-        return
-    # Don't do nose style setup/teardown on direct unittest style classes.
-    if isinstance(item, TestCaseFunction):
-        return
-
-    # Capture the narrowed type of item for the teardown closure,
-    # see https://github.com/python/mypy/issues/2608
-    func = item
-
-    call_optional(func.obj, "setup")
-    func.addfinalizer(lambda: call_optional(func.obj, "teardown"))
-
-    # NOTE: Module- and class-level fixtures are handled in python.py
-    # with `pluginmanager.has_plugin("nose")` checks.
-    # It would have been nicer to implement them outside of core, but
-    # it's not straightforward.
-
-
-def call_optional(obj: object, name: str) -> bool:
-    method = getattr(obj, name, None)
-    if method is None:
-        return False
-    is_fixture = getfixturemarker(method) is not None
-    if is_fixture:
-        return False
-    if not callable(method):
-        return False
-    # If there are any problems allow the exception to raise rather than
-    # silently ignoring it.
-    method()
-    return True
diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py
index 25206fe0e85..68ba0543365 100644
--- a/src/_pytest/outcomes.py
+++ b/src/_pytest/outcomes.py
@@ -1,35 +1,24 @@
 """Exception classes and constants handling test outcomes as well as
 functions creating them."""
+
+from __future__ import annotations
+
+from collections.abc import Callable
 import sys
-import warnings
 from typing import Any
-from typing import Callable
 from typing import cast
-from typing import Optional
-from typing import Type
+from typing import NoReturn
+from typing import Protocol
 from typing import TypeVar
 
-from _pytest.deprecated import KEYWORD_MSG_ARG
-
-TYPE_CHECKING = False  # Avoid circular import through compat.
-
-if TYPE_CHECKING:
-    from typing import NoReturn
-    from typing_extensions import Protocol
-else:
-    # typing.Protocol is only available starting from Python 3.8. It is also
-    # available from typing_extensions, but we don't want a runtime dependency
-    # on that. So use a dummy runtime implementation.
-    from typing import Generic
-
-    Protocol = Generic
+from .warning_types import PytestDeprecationWarning
 
 
 class OutcomeException(BaseException):
     """OutcomeException and its subclass instances indicate and contain info
     about test and collection outcomes."""
 
-    def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None:
+    def __init__(self, msg: str | None = None, pytrace: bool = True) -> None:
         if msg is not None and not isinstance(msg, str):
             error_msg = (  # type: ignore[unreachable]
                 "{} expected string as 'msg' parameter, got '{}' instead.\n"
@@ -58,7 +47,7 @@ class Skipped(OutcomeException):
 
     def __init__(
         self,
-        msg: Optional[str] = None,
+        msg: str | None = None,
         pytrace: bool = True,
         allow_module_level: bool = False,
         *,
@@ -81,18 +70,18 @@ class Exit(Exception):
     """Raised for immediate program exits (no tracebacks/summaries)."""
 
     def __init__(
-        self, msg: str = "unknown reason", returncode: Optional[int] = None
+        self, msg: str = "unknown reason", returncode: int | None = None
     ) -> None:
         self.msg = msg
         self.returncode = returncode
         super().__init__(msg)
 
 
-# Elaborate hack to work around https://github.com/python/mypy/issues/2087.
-# Ideally would just be `exit.Exception = Exit` etc.
+# We need a callable protocol to add attributes, for discussion see
+# https://github.com/python/mypy/issues/2087.
 
 _F = TypeVar("_F", bound=Callable[..., object])
-_ET = TypeVar("_ET", bound=Type[BaseException])
+_ET = TypeVar("_ET", bound=type[BaseException])
 
 
 class _WithException(Protocol[_F, _ET]):
@@ -114,8 +103,9 @@ def decorate(func: _F) -> _WithException[_F, _ET]:
 
 @_with_exception(Exit)
 def exit(
-    reason: str = "", returncode: Optional[int] = None, *, msg: Optional[str] = None
-) -> "NoReturn":
+    reason: str = "",
+    returncode: int | None = None,
+) -> NoReturn:
     """Exit testing process.
 
     :param reason:
@@ -123,30 +113,21 @@ def exit(
         only because `msg` is deprecated.
 
     :param returncode:
-        Return code to be used when exiting pytest.
+        Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`.
 
-    :param msg:
-        Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
+    :raises pytest.exit.Exception:
+        The exception that is raised.
     """
     __tracebackhide__ = True
-    from _pytest.config import UsageError
-
-    if reason and msg:
-        raise UsageError(
-            "cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`."
-        )
-    if not reason:
-        if msg is None:
-            raise UsageError("exit() requires a reason argument")
-        warnings.warn(KEYWORD_MSG_ARG.format(func="exit"), stacklevel=2)
-        reason = msg
     raise Exit(reason, returncode)
 
 
 @_with_exception(Skipped)
 def skip(
-    reason: str = "", *, allow_module_level: bool = False, msg: Optional[str] = None
-) -> "NoReturn":
+    reason: str = "",
+    *,
+    allow_module_level: bool = False,
+) -> NoReturn:
     """Skip an executing test with the given message.
 
     This function should be called only during testing (setup, call or teardown) or
@@ -157,11 +138,15 @@ def skip(
         The message to show the user as reason for the skip.
 
     :param allow_module_level:
-        Allows this function to be called at module level, skipping the rest
-        of the module. Defaults to False.
+        Allows this function to be called at module level.
+        Raising the skip exception at module level will stop
+        the execution of the module and prevent the collection of all tests in the module,
+        even those defined before the `skip` call.
 
-    :param msg:
-        Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
+        Defaults to False.
+
+    :raises pytest.skip.Exception:
+        The exception that is raised.
 
     .. note::
         It is better to use the :ref:`pytest.mark.skipif ref` marker when
@@ -171,14 +156,11 @@ def skip(
         to skip a doctest statically.
     """
     __tracebackhide__ = True
-    reason = _resolve_msg_to_reason("skip", reason, msg)
     raise Skipped(msg=reason, allow_module_level=allow_module_level)
 
 
 @_with_exception(Failed)
-def fail(
-    reason: str = "", pytrace: bool = True, msg: Optional[str] = None
-) -> "NoReturn":
+def fail(reason: str = "", pytrace: bool = True) -> NoReturn:
     """Explicitly fail an executing test with the given message.
 
     :param reason:
@@ -188,108 +170,137 @@ def fail(
         If False, msg represents the full failure information and no
         python traceback will be reported.
 
-    :param msg:
-        Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
+    :raises pytest.fail.Exception:
+        The exception that is raised.
     """
     __tracebackhide__ = True
-    reason = _resolve_msg_to_reason("fail", reason, msg)
     raise Failed(msg=reason, pytrace=pytrace)
 
 
-def _resolve_msg_to_reason(
-    func_name: str, reason: str, msg: Optional[str] = None
-) -> str:
-    """
-    Handles converting the deprecated msg parameter if provided into
-    reason, raising a deprecation warning.  This function will be removed
-    when the optional msg argument is removed from here in future.
-
-    :param str func_name:
-        The name of the offending function, this is formatted into the deprecation message.
-
-    :param str reason:
-        The reason= passed into either pytest.fail() or pytest.skip()
-
-    :param str msg:
-        The msg= passed into either pytest.fail() or pytest.skip().  This will
-        be converted into reason if it is provided to allow pytest.skip(msg=) or
-        pytest.fail(msg=) to continue working in the interim period.
-
-    :returns:
-        The value to use as reason.
-
-    """
-    __tracebackhide__ = True
-    if msg is not None:
-
-        if reason:
-            from pytest import UsageError
-
-            raise UsageError(
-                f"Passing both ``reason`` and ``msg`` to pytest.{func_name}(...) is not permitted."
-            )
-        warnings.warn(KEYWORD_MSG_ARG.format(func=func_name), stacklevel=3)
-        reason = msg
-    return reason
-
-
 class XFailed(Failed):
     """Raised from an explicit call to pytest.xfail()."""
 
 
 @_with_exception(XFailed)
-def xfail(reason: str = "") -> "NoReturn":
+def xfail(reason: str = "") -> NoReturn:
     """Imperatively xfail an executing test or setup function with the given reason.
 
     This function should be called only during testing (setup, call or teardown).
 
+    No other code is executed after using ``xfail()`` (it is implemented
+    internally by raising an exception).
+
+    :param reason:
+        The message to show the user as reason for the xfail.
+
     .. note::
         It is better to use the :ref:`pytest.mark.xfail ref` marker when
         possible to declare a test to be xfailed under certain conditions
         like known bugs or missing features.
+
+    :raises pytest.xfail.Exception:
+        The exception that is raised.
     """
     __tracebackhide__ = True
     raise XFailed(reason)
 
 
 def importorskip(
-    modname: str, minversion: Optional[str] = None, reason: Optional[str] = None
+    modname: str,
+    minversion: str | None = None,
+    reason: str | None = None,
+    *,
+    exc_type: type[ImportError] | None = None,
 ) -> Any:
     """Import and return the requested module ``modname``, or skip the
     current test if the module cannot be imported.
 
-    :param str modname:
+    :param modname:
         The name of the module to import.
-    :param str minversion:
+    :param minversion:
         If given, the imported module's ``__version__`` attribute must be at
         least this minimal version, otherwise the test is still skipped.
-    :param str reason:
+    :param reason:
         If given, this reason is shown as the message when the module cannot
         be imported.
+    :param exc_type:
+        The exception that should be captured in order to skip modules.
+        Must be :py:class:`ImportError` or a subclass.
+
+        If the module can be imported but raises :class:`ImportError`, pytest will
+        issue a warning to the user, as often users expect the module not to be
+        found (which would raise :class:`ModuleNotFoundError` instead).
+
+        This warning can be suppressed by passing ``exc_type=ImportError`` explicitly.
+
+        See :ref:`import-or-skip-import-error` for details.
+
 
     :returns:
         The imported module. This should be assigned to its canonical name.
 
+    :raises pytest.skip.Exception:
+        If the module cannot be imported.
+
     Example::
 
         docutils = pytest.importorskip("docutils")
+
+    .. versionadded:: 8.2
+
+        The ``exc_type`` parameter.
     """
     import warnings
 
     __tracebackhide__ = True
     compile(modname, "", "eval")  # to catch syntaxerrors
 
+    # Until pytest 9.1, we will warn the user if we catch ImportError (instead of ModuleNotFoundError),
+    # as this might be hiding an installation/environment problem, which is not usually what is intended
+    # when using importorskip() (#11523).
+    # In 9.1, to keep the function signature compatible, we just change the code below to:
+    # 1. Use `exc_type = ModuleNotFoundError` if `exc_type` is not given.
+    # 2. Remove `warn_on_import` and the warning handling.
+    if exc_type is None:
+        exc_type = ImportError
+        warn_on_import_error = True
+    else:
+        warn_on_import_error = False
+
+    skipped: Skipped | None = None
+    warning: Warning | None = None
+
     with warnings.catch_warnings():
         # Make sure to ignore ImportWarnings that might happen because
         # of existing directories with the same name we're trying to
         # import but without a __init__.py file.
         warnings.simplefilter("ignore")
+
         try:
             __import__(modname)
-        except ImportError as exc:
+        except exc_type as exc:
+            # Do not raise or issue warnings inside the catch_warnings() block.
             if reason is None:
                 reason = f"could not import {modname!r}: {exc}"
-            raise Skipped(reason, allow_module_level=True) from None
+            skipped = Skipped(reason, allow_module_level=True)
+
+            if warn_on_import_error and not isinstance(exc, ModuleNotFoundError):
+                lines = [
+                    "",
+                    f"Module '{modname}' was found, but when imported by pytest it raised:",
+                    f"    {exc!r}",
+                    "In pytest 9.1 this warning will become an error by default.",
+                    "You can fix the underlying problem, or alternatively overwrite this behavior and silence this "
+                    "warning by passing exc_type=ImportError explicitly.",
+                    "See https://docs.pytest.org/en/stable/deprecations.html#pytest-importorskip-default-behavior-regarding-importerror",
+                ]
+                warning = PytestDeprecationWarning("\n".join(lines))
+
+    if warning:
+        warnings.warn(warning, stacklevel=2)
+    if skipped:
+        raise skipped
+
     mod = sys.modules[modname]
     if minversion is None:
         return mod
@@ -300,8 +311,7 @@ def importorskip(
 
         if verattr is None or Version(verattr) < Version(minversion):
             raise Skipped(
-                "module %r has __version__ %r, required is: %r"
-                % (modname, verattr, minversion),
+                f"module {modname!r} has __version__ {verattr!r}, required is: {minversion!r}",
                 allow_module_level=True,
             )
     return mod
diff --git a/src/_pytest/pastebin.py b/src/_pytest/pastebin.py
index 385b3022cc0..d5c4f29c4c3 100644
--- a/src/_pytest/pastebin.py
+++ b/src/_pytest/pastebin.py
@@ -1,15 +1,18 @@
+# mypy: allow-untyped-defs
 """Submit failure or test session information to a pastebin service."""
-import tempfile
+
+from __future__ import annotations
+
 from io import StringIO
+import tempfile
 from typing import IO
-from typing import Union
 
-import pytest
 from _pytest.config import Config
 from _pytest.config import create_terminal_writer
 from _pytest.config.argparsing import Parser
 from _pytest.stash import StashKey
 from _pytest.terminal import TerminalReporter
+import pytest
 
 
 pastebinfile_key = StashKey[IO[bytes]]()
@@ -17,14 +20,14 @@
 
 def pytest_addoption(parser: Parser) -> None:
     group = parser.getgroup("terminal reporting")
-    group._addoption(
+    group.addoption(
         "--pastebin",
         metavar="mode",
         action="store",
         dest="pastebin",
         default=None,
         choices=["failed", "all"],
-        help="send failed|all info to bpaste.net pastebin service.",
+        help="Send failed|all info to bpaste.net pastebin service",
     )
 
 
@@ -63,18 +66,18 @@ def pytest_unconfigure(config: Config) -> None:
         # Write summary.
         tr.write_sep("=", "Sending information to Paste Service")
         pastebinurl = create_new_paste(sessionlog)
-        tr.write_line("pastebin session-log: %s\n" % pastebinurl)
+        tr.write_line(f"pastebin session-log: {pastebinurl}\n")
 
 
-def create_new_paste(contents: Union[str, bytes]) -> str:
+def create_new_paste(contents: str | bytes) -> str:
     """Create a new paste using the bpaste.net service.
 
     :contents: Paste contents string.
     :returns: URL to the pasted contents, or an error message.
     """
     import re
-    from urllib.request import urlopen
     from urllib.parse import urlencode
+    from urllib.request import urlopen
 
     params = {"code": contents, "lexer": "text", "expiry": "1week"}
     url = "https://bpa.st"
@@ -83,7 +86,7 @@ def create_new_paste(contents: Union[str, bytes]) -> str:
             urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8")
         )
     except OSError as exc_info:  # urllib errors
-        return "bad response: %s" % exc_info
+        return f"bad response: {exc_info}"
     m = re.search(r'href="/raw/(\w+)"', response)
     if m:
         return f"{url}/show/{m.group(1)}"
diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py
index b44753e1a41..b69e85404e7 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -1,19 +1,22 @@
+from __future__ import annotations
+
 import atexit
+from collections.abc import Callable
+from collections.abc import Iterable
+from collections.abc import Iterator
 import contextlib
-import fnmatch
-import importlib.util
-import itertools
-import os
-import shutil
-import sys
-import uuid
-import warnings
 from enum import Enum
 from errno import EBADF
 from errno import ELOOP
 from errno import ENOENT
 from errno import ENOTDIR
+import fnmatch
 from functools import partial
+from importlib.machinery import ModuleSpec
+from importlib.machinery import PathFinder
+import importlib.util
+import itertools
+import os
 from os.path import expanduser
 from os.path import expandvars
 from os.path import isabs
@@ -21,22 +24,26 @@
 from pathlib import Path
 from pathlib import PurePath
 from posixpath import sep as posix_sep
+import shutil
+import sys
+import types
 from types import ModuleType
-from typing import Callable
-from typing import Dict
-from typing import Iterable
-from typing import Iterator
-from typing import Optional
-from typing import Set
+from typing import Any
 from typing import TypeVar
-from typing import Union
+import uuid
+import warnings
 
 from _pytest.compat import assert_never
 from _pytest.outcomes import skip
 from _pytest.warning_types import PytestWarning
 
-LOCK_TIMEOUT = 60 * 60 * 24 * 3
 
+if sys.version_info < (3, 11):
+    from importlib._bootstrap_external import _NamespaceLoader as NamespaceLoader
+else:
+    from importlib.machinery import NamespaceLoader
+
+LOCK_TIMEOUT = 60 * 60 * 24 * 3
 
 _AnyPurePath = TypeVar("_AnyPurePath", bound=PurePath)
 
@@ -52,7 +59,7 @@
 )
 
 
-def _ignore_error(exception):
+def _ignore_error(exception: Exception) -> bool:
     return (
         getattr(exception, "errno", None) in _IGNORED_ERRORS
         or getattr(exception, "winerror", None) in _IGNORED_WINERRORS
@@ -63,21 +70,31 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath:
     return path.joinpath(".lock")
 
 
-def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
+def on_rm_rf_error(
+    func: Callable[..., Any] | None,
+    path: str,
+    excinfo: BaseException
+    | tuple[type[BaseException], BaseException, types.TracebackType | None],
+    *,
+    start_path: Path,
+) -> bool:
     """Handle known read-only errors during rmtree.
 
     The returned value is used only by our own tests.
     """
-    exctype, excvalue = exc[:2]
+    if isinstance(excinfo, BaseException):
+        exc = excinfo
+    else:
+        exc = excinfo[1]
 
     # Another process removed the file in the middle of the "rm_rf" (xdist for example).
     # More context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018
-    if isinstance(excvalue, FileNotFoundError):
+    if isinstance(exc, FileNotFoundError):
         return False
 
-    if not isinstance(excvalue, PermissionError):
+    if not isinstance(exc, PermissionError):
         warnings.warn(
-            PytestWarning(f"(rm_rf) error removing {path}\n{exctype}: {excvalue}")
+            PytestWarning(f"(rm_rf) error removing {path}\n{type(exc)}: {exc}")
         )
         return False
 
@@ -85,9 +102,7 @@ def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
         if func not in (os.open,):
             warnings.warn(
                 PytestWarning(
-                    "(rm_rf) unknown function {} when removing {}:\n{}: {}".format(
-                        func, path, exctype, excvalue
-                    )
+                    f"(rm_rf) unknown function {func} when removing {path}:\n{type(exc)}: {exc}"
                 )
             )
         return False
@@ -149,26 +164,29 @@ def rm_rf(path: Path) -> None:
     are read-only."""
     path = ensure_extended_length_path(path)
     onerror = partial(on_rm_rf_error, start_path=path)
-    shutil.rmtree(str(path), onerror=onerror)
+    if sys.version_info >= (3, 12):
+        shutil.rmtree(str(path), onexc=onerror)
+    else:
+        shutil.rmtree(str(path), onerror=onerror)
 
 
-def find_prefixed(root: Path, prefix: str) -> Iterator[Path]:
-    """Find all elements in root that begin with the prefix, case insensitive."""
+def find_prefixed(root: Path, prefix: str) -> Iterator[os.DirEntry[str]]:
+    """Find all elements in root that begin with the prefix, case-insensitive."""
     l_prefix = prefix.lower()
-    for x in root.iterdir():
+    for x in os.scandir(root):
         if x.name.lower().startswith(l_prefix):
             yield x
 
 
-def extract_suffixes(iter: Iterable[PurePath], prefix: str) -> Iterator[str]:
+def extract_suffixes(iter: Iterable[os.DirEntry[str]], prefix: str) -> Iterator[str]:
     """Return the parts of the paths following the prefix.
 
     :param iter: Iterator over path names.
     :param prefix: Expected prefix of the path names.
     """
     p_len = len(prefix)
-    for p in iter:
-        yield p.name[p_len:]
+    for entry in iter:
+        yield entry.name[p_len:]
 
 
 def find_suffixes(root: Path, prefix: str) -> Iterator[str]:
@@ -176,7 +194,7 @@ def find_suffixes(root: Path, prefix: str) -> Iterator[str]:
     return extract_suffixes(find_prefixed(root, prefix), prefix)
 
 
-def parse_num(maybe_num) -> int:
+def parse_num(maybe_num: str) -> int:
     """Parse number path suffixes, returns -1 on error."""
     try:
         return int(maybe_num)
@@ -184,9 +202,7 @@ def parse_num(maybe_num) -> int:
         return -1
 
 
-def _force_symlink(
-    root: Path, target: Union[str, PurePath], link_to: Union[str, Path]
-) -> None:
+def _force_symlink(root: Path, target: str | PurePath, link_to: str | Path) -> None:
     """Helper to create the current symlink.
 
     It's full of race conditions that are reasonably OK to ignore
@@ -223,7 +239,7 @@ def make_numbered_dir(root: Path, prefix: str, mode: int = 0o700) -> Path:
     else:
         raise OSError(
             "could not create numbered dir with prefix "
-            "{prefix} in {root} after 10 tries".format(prefix=prefix, root=root)
+            f"{prefix} in {root} after 10 tries"
         )
 
 
@@ -244,7 +260,9 @@ def create_cleanup_lock(p: Path) -> Path:
         return lock_path
 
 
-def register_cleanup_lock_removal(lock_path: Path, register=atexit.register):
+def register_cleanup_lock_removal(
+    lock_path: Path, register: Any = atexit.register
+) -> Any:
     """Register a cleanup function for removing a lock, by default on atexit."""
     pid = os.getpid()
 
@@ -327,23 +345,34 @@ def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]:
     """List candidates for numbered directories to be removed - follows py.path."""
     max_existing = max(map(parse_num, find_suffixes(root, prefix)), default=-1)
     max_delete = max_existing - keep
-    paths = find_prefixed(root, prefix)
-    paths, paths2 = itertools.tee(paths)
-    numbers = map(parse_num, extract_suffixes(paths2, prefix))
-    for path, number in zip(paths, numbers):
+    entries = find_prefixed(root, prefix)
+    entries, entries2 = itertools.tee(entries)
+    numbers = map(parse_num, extract_suffixes(entries2, prefix))
+    for entry, number in zip(entries, numbers):
         if number <= max_delete:
-            yield path
+            yield Path(entry)
+
+
+def cleanup_dead_symlinks(root: Path) -> None:
+    for left_dir in root.iterdir():
+        if left_dir.is_symlink():
+            if not left_dir.resolve().exists():
+                left_dir.unlink()
 
 
 def cleanup_numbered_dir(
     root: Path, prefix: str, keep: int, consider_lock_dead_if_created_before: float
 ) -> None:
     """Cleanup for lock driven numbered directories."""
+    if not root.exists():
+        return
     for path in cleanup_candidates(root, prefix, keep):
         try_cleanup(path, consider_lock_dead_if_created_before)
     for path in root.glob("garbage-*"):
         try_cleanup(path, consider_lock_dead_if_created_before)
 
+    cleanup_dead_symlinks(root)
+
 
 def make_numbered_dir_with_cleanup(
     root: Path,
@@ -357,8 +386,10 @@ def make_numbered_dir_with_cleanup(
     for i in range(10):
         try:
             p = make_numbered_dir(root, prefix, mode)
-            lock_path = create_cleanup_lock(p)
-            register_cleanup_lock_removal(lock_path)
+            # Only lock the current dir when keep is not 0
+            if keep != 0:
+                lock_path = create_cleanup_lock(p)
+                register_cleanup_lock_removal(lock_path)
         except Exception as exc:
             e = exc
         else:
@@ -385,7 +416,7 @@ def resolve_from_str(input: str, rootpath: Path) -> Path:
         return rootpath.joinpath(input)
 
 
-def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool:
+def fnmatch_ex(pattern: str, path: str | os.PathLike[str]) -> bool:
     """A port of FNMatcher from py.path.common which works with PurePath() instances.
 
     The difference between this algorithm and PurePath.match() is that the
@@ -421,15 +452,19 @@ def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool:
     return fnmatch.fnmatch(name, pattern)
 
 
-def parts(s: str) -> Set[str]:
+def parts(s: str) -> set[str]:
     parts = s.split(sep)
     return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
 
 
-def symlink_or_skip(src, dst, **kwargs):
+def symlink_or_skip(
+    src: os.PathLike[str] | str,
+    dst: os.PathLike[str] | str,
+    **kwargs: Any,
+) -> None:
     """Make a symlink, or skip the test in case symlinks are not supported."""
     try:
-        os.symlink(str(src), str(dst), **kwargs)
+        os.symlink(src, dst, **kwargs)
     except OSError as e:
         skip(f"symlinks not supported: {e}")
 
@@ -452,71 +487,90 @@ class ImportPathMismatchError(ImportError):
 
 
 def import_path(
-    p: Union[str, "os.PathLike[str]"],
+    path: str | os.PathLike[str],
     *,
-    mode: Union[str, ImportMode] = ImportMode.prepend,
+    mode: str | ImportMode = ImportMode.prepend,
     root: Path,
+    consider_namespace_packages: bool,
 ) -> ModuleType:
-    """Import and return a module from the given path, which can be a file (a module) or
+    """
+    Import and return a module from the given path, which can be a file (a module) or
     a directory (a package).
 
-    The import mechanism used is controlled by the `mode` parameter:
+    :param path:
+        Path to the file to import.
+
+    :param mode:
+        Controls the underlying import mechanism that will be used:
 
-    * `mode == ImportMode.prepend`: the directory containing the module (or package, taking
-      `__init__.py` files into account) will be put at the *start* of `sys.path` before
-      being imported with `__import__.
+        * ImportMode.prepend: the directory containing the module (or package, taking
+          `__init__.py` files into account) will be put at the *start* of `sys.path` before
+          being imported with `importlib.import_module`.
 
-    * `mode == ImportMode.append`: same as `prepend`, but the directory will be appended
-      to the end of `sys.path`, if not already in `sys.path`.
+        * ImportMode.append: same as `prepend`, but the directory will be appended
+          to the end of `sys.path`, if not already in `sys.path`.
 
-    * `mode == ImportMode.importlib`: uses more fine control mechanisms provided by `importlib`
-      to import the module, which avoids having to use `__import__` and muck with `sys.path`
-      at all. It effectively allows having same-named test modules in different places.
+        * ImportMode.importlib: uses more fine control mechanisms provided by `importlib`
+          to import the module, which avoids having to muck with `sys.path` at all. It effectively
+          allows having same-named test modules in different places.
 
     :param root:
         Used as an anchor when mode == ImportMode.importlib to obtain
         a unique name for the module being imported so it can safely be stored
         into ``sys.modules``.
 
+    :param consider_namespace_packages:
+        If True, consider namespace packages when resolving module names.
+
     :raises ImportPathMismatchError:
         If after importing the given `path` and the module `__file__`
         are different. Only raised in `prepend` and `append` modes.
     """
+    path = Path(path)
     mode = ImportMode(mode)
 
-    path = Path(p)
-
     if not path.exists():
         raise ImportError(path)
 
     if mode is ImportMode.importlib:
-        module_name = module_name_from_path(path, root)
-
-        for meta_importer in sys.meta_path:
-            spec = meta_importer.find_spec(module_name, [str(path.parent)])
-            if spec is not None:
-                break
+        # Try to import this module using the standard import mechanisms, but
+        # without touching sys.path.
+        try:
+            pkg_root, module_name = resolve_pkg_root_and_module_name(
+                path, consider_namespace_packages=consider_namespace_packages
+            )
+        except CouldNotResolvePathError:
+            pass
         else:
-            spec = importlib.util.spec_from_file_location(module_name, str(path))
+            # If the given module name is already in sys.modules, do not import it again.
+            with contextlib.suppress(KeyError):
+                return sys.modules[module_name]
+
+            mod = _import_module_using_spec(
+                module_name, path, pkg_root, insert_modules=False
+            )
+            if mod is not None:
+                return mod
+
+        # Could not import the module with the current sys.path, so we fall back
+        # to importing the file as a single module, not being a part of a package.
+        module_name = module_name_from_path(path, root)
+        with contextlib.suppress(KeyError):
+            return sys.modules[module_name]
 
-        if spec is None:
+        mod = _import_module_using_spec(
+            module_name, path, path.parent, insert_modules=True
+        )
+        if mod is None:
             raise ImportError(f"Can't find module {module_name} at location {path}")
-        mod = importlib.util.module_from_spec(spec)
-        sys.modules[module_name] = mod
-        spec.loader.exec_module(mod)  # type: ignore[union-attr]
-        insert_missing_modules(sys.modules, module_name)
         return mod
 
-    pkg_path = resolve_package_path(path)
-    if pkg_path is not None:
-        pkg_root = pkg_path.parent
-        names = list(path.with_suffix("").relative_to(pkg_root).parts)
-        if names[-1] == "__init__":
-            names.pop()
-        module_name = ".".join(names)
-    else:
-        pkg_root = path.parent
-        module_name = path.stem
+    try:
+        pkg_root, module_name = resolve_pkg_root_and_module_name(
+            path, consider_namespace_packages=consider_namespace_packages
+        )
+    except CouldNotResolvePathError:
+        pkg_root, module_name = path.parent, path.stem
 
     # Change sys.path permanently: restoring it at the end of this function would cause surprising
     # problems because of delayed imports: for example, a conftest.py file imported by this function
@@ -539,10 +593,13 @@ def import_path(
     ignore = os.environ.get("PY_IGNORE_IMPORTMISMATCH", "")
     if ignore != "1":
         module_file = mod.__file__
+        if module_file is None:
+            raise ImportPathMismatchError(module_name, module_file, path)
+
         if module_file.endswith((".pyc", ".pyo")):
             module_file = module_file[:-1]
-        if module_file.endswith(os.path.sep + "__init__.py"):
-            module_file = module_file[: -(len(os.path.sep + "__init__.py"))]
+        if module_file.endswith(os.sep + "__init__.py"):
+            module_file = module_file[: -(len(os.sep + "__init__.py"))]
 
         try:
             is_same = _is_same(str(path), module_file)
@@ -555,6 +612,148 @@ def import_path(
     return mod
 
 
+def _import_module_using_spec(
+    module_name: str, module_path: Path, module_location: Path, *, insert_modules: bool
+) -> ModuleType | None:
+    """
+    Tries to import a module by its canonical name, path, and its parent location.
+
+    :param module_name:
+        The expected module name, will become the key of `sys.modules`.
+
+    :param module_path:
+        The file path of the module, for example `/foo/bar/test_demo.py`.
+        If module is a package, pass the path to the  `__init__.py` of the package.
+        If module is a namespace package, pass directory path.
+
+    :param module_location:
+        The parent location of the module.
+        If module is a package, pass the directory containing the `__init__.py` file.
+
+    :param insert_modules:
+        If True, will call `insert_missing_modules` to create empty intermediate modules
+        with made-up module names (when importing test files not reachable from `sys.path`).
+
+    Example 1 of parent_module_*:
+
+        module_name:        "a.b.c.demo"
+        module_path:        Path("a/b/c/demo.py")
+        module_location:    Path("a/b/c/")
+        if "a.b.c" is package ("a/b/c/__init__.py" exists), then
+            parent_module_name:         "a.b.c"
+            parent_module_path:         Path("a/b/c/__init__.py")
+            parent_module_location:     Path("a/b/c/")
+        else:
+            parent_module_name:         "a.b.c"
+            parent_module_path:         Path("a/b/c")
+            parent_module_location:     Path("a/b/")
+
+    Example 2 of parent_module_*:
+
+        module_name:        "a.b.c"
+        module_path:        Path("a/b/c/__init__.py")
+        module_location:    Path("a/b/c/")
+        if  "a.b" is package ("a/b/__init__.py" exists), then
+            parent_module_name:         "a.b"
+            parent_module_path:         Path("a/b/__init__.py")
+            parent_module_location:     Path("a/b/")
+        else:
+            parent_module_name:         "a.b"
+            parent_module_path:         Path("a/b/")
+            parent_module_location:     Path("a/")
+    """
+    # Attempt to import the parent module, seems is our responsibility:
+    # https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311
+    parent_module_name, _, name = module_name.rpartition(".")
+    parent_module: ModuleType | None = None
+    if parent_module_name:
+        parent_module = sys.modules.get(parent_module_name)
+        # If the parent_module lacks the `__path__` attribute, AttributeError when finding a submodule's spec,
+        # requiring re-import according to the path.
+        need_reimport = not hasattr(parent_module, "__path__")
+        if parent_module is None or need_reimport:
+            # Get parent_location based on location, get parent_path based on path.
+            if module_path.name == "__init__.py":
+                # If the current module is in a package,
+                # need to leave the package first and then enter the parent module.
+                parent_module_path = module_path.parent.parent
+            else:
+                parent_module_path = module_path.parent
+
+            if (parent_module_path / "__init__.py").is_file():
+                # If the parent module is a package, loading by  __init__.py file.
+                parent_module_path = parent_module_path / "__init__.py"
+
+            parent_module = _import_module_using_spec(
+                parent_module_name,
+                parent_module_path,
+                parent_module_path.parent,
+                insert_modules=insert_modules,
+            )
+
+    # Checking with sys.meta_path first in case one of its hooks can import this module,
+    # such as our own assertion-rewrite hook.
+    for meta_importer in sys.meta_path:
+        module_name_of_meta = getattr(meta_importer.__class__, "__module__", "")
+        if module_name_of_meta == "_pytest.assertion.rewrite" and module_path.is_file():
+            # Import modules in subdirectories by module_path
+            # to ensure assertion rewrites are not missed (#12659).
+            find_spec_path = [str(module_location), str(module_path)]
+        else:
+            find_spec_path = [str(module_location)]
+
+        spec = meta_importer.find_spec(module_name, find_spec_path)
+
+        if spec_matches_module_path(spec, module_path):
+            break
+    else:
+        loader = None
+        if module_path.is_dir():
+            # The `spec_from_file_location` matches a loader based on the file extension by default.
+            # For a namespace package, need to manually specify a loader.
+            loader = NamespaceLoader(name, module_path, PathFinder())  # type: ignore[arg-type]
+
+        spec = importlib.util.spec_from_file_location(
+            module_name, str(module_path), loader=loader
+        )
+
+    if spec_matches_module_path(spec, module_path):
+        assert spec is not None
+        # Find spec and import this module.
+        mod = importlib.util.module_from_spec(spec)
+        sys.modules[module_name] = mod
+        spec.loader.exec_module(mod)  # type: ignore[union-attr]
+
+        # Set this module as an attribute of the parent module (#12194).
+        if parent_module is not None:
+            setattr(parent_module, name, mod)
+
+        if insert_modules:
+            insert_missing_modules(sys.modules, module_name)
+        return mod
+
+    return None
+
+
+def spec_matches_module_path(module_spec: ModuleSpec | None, module_path: Path) -> bool:
+    """Return true if the given ModuleSpec can be used to import the given module path."""
+    if module_spec is None:
+        return False
+
+    if module_spec.origin:
+        return Path(module_spec.origin) == module_path
+
+    # Compare the path with the `module_spec.submodule_Search_Locations` in case
+    # the module is part of a namespace package.
+    # https://docs.python.org/3/library/importlib.html#importlib.machinery.ModuleSpec.submodule_search_locations
+    if module_spec.submodule_search_locations:  # can be None.
+        for path in module_spec.submodule_search_locations:
+            if Path(path) == module_path:
+                return True
+
+    return False
+
+
 # Implement a special _is_same function on Windows which returns True if the two filenames
 # compare equal, to circumvent os.path.samefile returning False for mounts in UNC (#7678).
 if sys.platform.startswith("win"):
@@ -562,7 +761,6 @@ def import_path(
     def _is_same(f1: str, f2: str) -> bool:
         return Path(f1) == Path(f2) or os.path.samefile(f1, f2)
 
-
 else:
 
     def _is_same(f1: str, f2: str) -> bool:
@@ -587,10 +785,20 @@ def module_name_from_path(path: Path, root: Path) -> str:
         # Use the parts for the relative path to the root path.
         path_parts = relative_path.parts
 
+    # Module name for packages do not contain the __init__ file, unless
+    # the `__init__.py` file is at the root.
+    if len(path_parts) >= 2 and path_parts[-1] == "__init__":
+        path_parts = path_parts[:-1]
+
+    # Module names cannot contain ".", normalize them to "_". This prevents
+    # a directory having a "." in the name (".env.310" for example) causing extra intermediate modules.
+    # Also, important to replace "." at the start of paths, as those are considered relative imports.
+    path_parts = tuple(x.replace(".", "_") for x in path_parts)
+
     return ".".join(path_parts)
 
 
-def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> None:
+def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> None:
     """
     Used by ``import_path`` to create intermediate modules when using mode=importlib.
 
@@ -600,26 +808,44 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) ->
     """
     module_parts = module_name.split(".")
     while module_name:
-        if module_name not in modules:
-            module = ModuleType(
-                module_name,
-                doc="Empty module created by pytest's importmode=importlib.",
-            )
-            modules[module_name] = module
+        parent_module_name, _, child_name = module_name.rpartition(".")
+        if parent_module_name:
+            parent_module = modules.get(parent_module_name)
+            if parent_module is None:
+                try:
+                    # If sys.meta_path is empty, calling import_module will issue
+                    # a warning and raise ModuleNotFoundError. To avoid the
+                    # warning, we check sys.meta_path explicitly and raise the error
+                    # ourselves to fall back to creating a dummy module.
+                    if not sys.meta_path:
+                        raise ModuleNotFoundError
+                    parent_module = importlib.import_module(parent_module_name)
+                except ModuleNotFoundError:
+                    parent_module = ModuleType(
+                        module_name,
+                        doc="Empty module created by pytest's importmode=importlib.",
+                    )
+                modules[parent_module_name] = parent_module
+
+            # Add child attribute to the parent that can reference the child
+            # modules.
+            if not hasattr(parent_module, child_name):
+                setattr(parent_module, child_name, modules[module_name])
+
         module_parts.pop(-1)
         module_name = ".".join(module_parts)
 
 
-def resolve_package_path(path: Path) -> Optional[Path]:
+def resolve_package_path(path: Path) -> Path | None:
     """Return the Python package path by looking for the last
     directory upwards which still contains an __init__.py.
 
-    Returns None if it can not be determined.
+    Returns None if it cannot be determined.
     """
     result = None
     for parent in itertools.chain((path,), path.parents):
         if parent.is_dir():
-            if not parent.joinpath("__init__.py").is_file():
+            if not (parent / "__init__.py").is_file():
                 break
             if not parent.name.isidentifier():
                 break
@@ -627,45 +853,158 @@ def resolve_package_path(path: Path) -> Optional[Path]:
     return result
 
 
-def visit(
-    path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool]
-) -> Iterator["os.DirEntry[str]"]:
-    """Walk a directory recursively, in breadth-first order.
+def resolve_pkg_root_and_module_name(
+    path: Path, *, consider_namespace_packages: bool = False
+) -> tuple[Path, str]:
+    """
+    Return the path to the directory of the root package that contains the
+    given Python file, and its module name:
 
-    Entries at each directory level are sorted.
+        src/
+            app/
+                __init__.py
+                core/
+                    __init__.py
+                    models.py
+
+    Passing the full path to `models.py` will yield Path("src") and "app.core.models".
+
+    If consider_namespace_packages is True, then we additionally check upwards in the hierarchy
+    for namespace packages:
+
+    https://packaging.python.org/en/latest/guides/packaging-namespace-packages
+
+    Raises CouldNotResolvePathError if the given path does not belong to a package (missing any __init__.py files).
     """
+    pkg_root: Path | None = None
+    pkg_path = resolve_package_path(path)
+    if pkg_path is not None:
+        pkg_root = pkg_path.parent
+    if consider_namespace_packages:
+        start = pkg_root if pkg_root is not None else path.parent
+        for candidate in (start, *start.parents):
+            module_name = compute_module_name(candidate, path)
+            if module_name and is_importable(module_name, path):
+                # Point the pkg_root to the root of the namespace package.
+                pkg_root = candidate
+                break
+
+    if pkg_root is not None:
+        module_name = compute_module_name(pkg_root, path)
+        if module_name:
+            return pkg_root, module_name
 
-    # Skip entries with symlink loops and other brokenness, so the caller doesn't
-    # have to deal with it.
+    raise CouldNotResolvePathError(f"Could not resolve for {path}")
+
+
+def is_importable(module_name: str, module_path: Path) -> bool:
+    """
+    Return if the given module path could be imported normally by Python, akin to the user
+    entering the REPL and importing the corresponding module name directly, and corresponds
+    to the module_path specified.
+
+    :param module_name:
+        Full module name that we want to check if is importable.
+        For example, "app.models".
+
+    :param module_path:
+        Full path to the python module/package we want to check if is importable.
+        For example, "/projects/src/app/models.py".
+    """
+    try:
+        # Note this is different from what we do in ``_import_module_using_spec``, where we explicitly search through
+        # sys.meta_path to be able to pass the path of the module that we want to import (``meta_importer.find_spec``).
+        # Using importlib.util.find_spec() is different, it gives the same results as trying to import
+        # the module normally in the REPL.
+        spec = importlib.util.find_spec(module_name)
+    except (ImportError, ValueError, ImportWarning):
+        return False
+    else:
+        return spec_matches_module_path(spec, module_path)
+
+
+def compute_module_name(root: Path, module_path: Path) -> str | None:
+    """Compute a module name based on a path and a root anchor."""
+    try:
+        path_without_suffix = module_path.with_suffix("")
+    except ValueError:
+        # Empty paths (such as Path.cwd()) might break meta_path hooks (like our own assertion rewriter).
+        return None
+
+    try:
+        relative = path_without_suffix.relative_to(root)
+    except ValueError:  # pragma: no cover
+        return None
+    names = list(relative.parts)
+    if not names:
+        return None
+    if names[-1] == "__init__":
+        names.pop()
+    return ".".join(names)
+
+
+class CouldNotResolvePathError(Exception):
+    """Custom exception raised by resolve_pkg_root_and_module_name."""
+
+
+def scandir(
+    path: str | os.PathLike[str],
+    sort_key: Callable[[os.DirEntry[str]], object] = lambda entry: entry.name,
+) -> list[os.DirEntry[str]]:
+    """Scan a directory recursively, in breadth-first order.
+
+    The returned entries are sorted according to the given key.
+    The default is to sort by name.
+    If the directory does not exist, return an empty list.
+    """
     entries = []
-    for entry in os.scandir(path):
-        try:
-            entry.is_file()
-        except OSError as err:
-            if _ignore_error(err):
-                continue
-            raise
-        entries.append(entry)
+    # Attempt to create a scandir iterator for the given path.
+    try:
+        scandir_iter = os.scandir(path)
+    except FileNotFoundError:
+        # If the directory does not exist, return an empty list.
+        return []
+    # Use the scandir iterator in a context manager to ensure it is properly closed.
+    with scandir_iter as s:
+        for entry in s:
+            try:
+                entry.is_file()
+            except OSError as err:
+                if _ignore_error(err):
+                    continue
+                # Reraise non-ignorable errors to avoid hiding issues.
+                raise
+            entries.append(entry)
+    entries.sort(key=sort_key)  # type: ignore[arg-type]
+    return entries
 
-    entries.sort(key=lambda entry: entry.name)
 
-    yield from entries
+def visit(
+    path: str | os.PathLike[str], recurse: Callable[[os.DirEntry[str]], bool]
+) -> Iterator[os.DirEntry[str]]:
+    """Walk a directory recursively, in breadth-first order.
 
+    The `recurse` predicate determines whether a directory is recursed.
+
+    Entries at each directory level are sorted.
+    """
+    entries = scandir(path)
+    yield from entries
     for entry in entries:
         if entry.is_dir() and recurse(entry):
             yield from visit(entry.path, recurse)
 
 
-def absolutepath(path: Union[Path, str]) -> Path:
+def absolutepath(path: str | os.PathLike[str]) -> Path:
     """Convert a path to an absolute path using os.path.abspath.
 
     Prefer this over Path.resolve() (see #6523).
     Prefer this over Path.absolute() (not public, doesn't normalize).
     """
-    return Path(os.path.abspath(str(path)))
+    return Path(os.path.abspath(path))
 
 
-def commonpath(path1: Path, path2: Path) -> Optional[Path]:
+def commonpath(path1: Path, path2: Path) -> Path | None:
     """Return the common part shared with the other path, or None if there is
     no common part.
 
@@ -706,19 +1045,11 @@ def bestrelpath(directory: Path, dest: Path) -> str:
     )
 
 
-# Originates from py. path.local.copy(), with siginficant trims and adjustments.
-# TODO(py38): Replace with shutil.copytree(..., symlinks=True, dirs_exist_ok=True)
-def copytree(source: Path, target: Path) -> None:
-    """Recursively copy a source directory to target."""
-    assert source.is_dir()
-    for entry in visit(source, recurse=lambda entry: not entry.is_symlink()):
-        x = Path(entry)
-        relpath = x.relative_to(source)
-        newx = target / relpath
-        newx.parent.mkdir(exist_ok=True)
-        if x.is_symlink():
-            newx.symlink_to(os.readlink(x))
-        elif x.is_file():
-            shutil.copyfile(x, newx)
-        elif x.is_dir():
-            newx.mkdir(exist_ok=True)
+def safe_exists(p: Path) -> bool:
+    """Like Path.exists(), but account for input arguments that might be too long (#11394)."""
+    try:
+        return p.exists()
+    except (ValueError, OSError):
+        # ValueError: stat: path too long for Windows
+        # OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect
+        return False
diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py
index 42e71ff917e..59839562031 100644
--- a/src/_pytest/pytester.py
+++ b/src/_pytest/pytester.py
@@ -1,36 +1,38 @@
+# mypy: allow-untyped-defs
 """(Disabled by default) support for testing pytest and pytest plugins.
 
 PYTEST_DONT_REWRITE
 """
+
+from __future__ import annotations
+
 import collections.abc
+from collections.abc import Callable
+from collections.abc import Generator
+from collections.abc import Iterable
+from collections.abc import Sequence
 import contextlib
+from fnmatch import fnmatch
 import gc
 import importlib
+from io import StringIO
+import locale
 import os
+from pathlib import Path
 import platform
 import re
 import shutil
 import subprocess
 import sys
 import traceback
-from fnmatch import fnmatch
-from io import StringIO
-from pathlib import Path
 from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import Generator
+from typing import Final
+from typing import final
 from typing import IO
-from typing import Iterable
-from typing import List
-from typing import Optional
+from typing import Literal
 from typing import overload
-from typing import Sequence
 from typing import TextIO
-from typing import Tuple
-from typing import Type
 from typing import TYPE_CHECKING
-from typing import Union
 from weakref import WeakKeyDictionary
 
 from iniconfig import IniConfig
@@ -39,7 +41,6 @@
 from _pytest import timing
 from _pytest._code import Source
 from _pytest.capture import _get_multicapture
-from _pytest.compat import final
 from _pytest.compat import NOTSET
 from _pytest.compat import NotSetType
 from _pytest.config import _PluggyPlugin
@@ -60,18 +61,14 @@
 from _pytest.outcomes import importorskip
 from _pytest.outcomes import skip
 from _pytest.pathlib import bestrelpath
-from _pytest.pathlib import copytree
 from _pytest.pathlib import make_numbered_dir
 from _pytest.reports import CollectReport
 from _pytest.reports import TestReport
 from _pytest.tmpdir import TempPathFactory
-from _pytest.warning_types import PytestWarning
+from _pytest.warning_types import PytestFDWarning
 
 
 if TYPE_CHECKING:
-    from typing_extensions import Final
-    from typing_extensions import Literal
-
     import pexpect
 
 
@@ -89,7 +86,7 @@ def pytest_addoption(parser: Parser) -> None:
         action="store_true",
         dest="lsof",
         default=False,
-        help="run FD checks if lsof is available",
+        help="Run FD checks if lsof is available",
     )
 
     parser.addoption(
@@ -98,13 +95,13 @@ def pytest_addoption(parser: Parser) -> None:
         dest="runpytest",
         choices=("inprocess", "subprocess"),
         help=(
-            "run pytest sub runs in tests using an 'inprocess' "
+            "Run pytest sub runs in tests using an 'inprocess' "
             "or 'subprocess' (python -m main) method"
         ),
     )
 
     parser.addini(
-        "pytester_example_dir", help="directory to take the pytester example files from"
+        "pytester_example_dir", help="Directory to take the pytester example files from"
     )
 
 
@@ -122,13 +119,19 @@ def pytest_configure(config: Config) -> None:
 
 
 class LsofFdLeakChecker:
-    def get_open_files(self) -> List[Tuple[str, str]]:
+    def get_open_files(self) -> list[tuple[str, str]]:
+        if sys.version_info >= (3, 11):
+            # New in Python 3.11, ignores utf-8 mode
+            encoding = locale.getencoding()
+        else:
+            encoding = locale.getpreferredencoding(False)
         out = subprocess.run(
             ("lsof", "-Ffn0", "-p", str(os.getpid())),
             stdout=subprocess.PIPE,
             stderr=subprocess.DEVNULL,
             check=True,
-            universal_newlines=True,
+            text=True,
+            encoding=encoding,
         ).stdout
 
         def isopen(line: str) -> bool:
@@ -161,36 +164,38 @@ def matching_platform(self) -> bool:
         else:
             return True
 
-    @hookimpl(hookwrapper=True, tryfirst=True)
-    def pytest_runtest_protocol(self, item: Item) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True, tryfirst=True)
+    def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object]:
         lines1 = self.get_open_files()
-        yield
-        if hasattr(sys, "pypy_version_info"):
-            gc.collect()
-        lines2 = self.get_open_files()
-
-        new_fds = {t[0] for t in lines2} - {t[0] for t in lines1}
-        leaked_files = [t for t in lines2 if t[0] in new_fds]
-        if leaked_files:
-            error = [
-                "***** %s FD leakage detected" % len(leaked_files),
-                *(str(f) for f in leaked_files),
-                "*** Before:",
-                *(str(f) for f in lines1),
-                "*** After:",
-                *(str(f) for f in lines2),
-                "***** %s FD leakage detected" % len(leaked_files),
-                "*** function %s:%s: %s " % item.location,
-                "See issue #2366",
-            ]
-            item.warn(PytestWarning("\n".join(error)))
+        try:
+            return (yield)
+        finally:
+            if hasattr(sys, "pypy_version_info"):
+                gc.collect()
+            lines2 = self.get_open_files()
+
+            new_fds = {t[0] for t in lines2} - {t[0] for t in lines1}
+            leaked_files = [t for t in lines2 if t[0] in new_fds]
+            if leaked_files:
+                error = [
+                    f"***** {len(leaked_files)} FD leakage detected",
+                    *(str(f) for f in leaked_files),
+                    "*** Before:",
+                    *(str(f) for f in lines1),
+                    "*** After:",
+                    *(str(f) for f in lines2),
+                    f"***** {len(leaked_files)} FD leakage detected",
+                    "*** function {}:{}: {} ".format(*item.location),
+                    "See issue #2366",
+                ]
+                item.warn(PytestFDWarning("\n".join(error)))
 
 
 # used at least by pytest-xdist plugin
 
 
 @fixture
-def _pytest(request: FixtureRequest) -> "PytestArg":
+def _pytest(request: FixtureRequest) -> PytestArg:
     """Return a helper which offers a gethookrecorder(hook) method which
     returns a HookRecorder instance which helps to make assertions about called
     hooks."""
@@ -201,13 +206,13 @@ class PytestArg:
     def __init__(self, request: FixtureRequest) -> None:
         self._request = request
 
-    def gethookrecorder(self, hook) -> "HookRecorder":
+    def gethookrecorder(self, hook) -> HookRecorder:
         hookrecorder = HookRecorder(hook._pm)
         self._request.addfinalizer(hookrecorder.finish_recording)
         return hookrecorder
 
 
-def get_public_names(values: Iterable[str]) -> List[str]:
+def get_public_names(values: Iterable[str]) -> list[str]:
     """Only return names from iterator values without a leading underscore."""
     return [x for x in values if x[0] != "_"]
 
@@ -237,8 +242,7 @@ def __repr__(self) -> str:
 
     if TYPE_CHECKING:
         # The class has undetermined attributes, this tells mypy about it.
-        def __getattr__(self, key: str):
-            ...
+        def __getattr__(self, key: str): ...
 
 
 @final
@@ -257,8 +261,8 @@ def __init__(
         check_ispytest(_ispytest)
 
         self._pluginmanager = pluginmanager
-        self.calls: List[RecordedHookCall] = []
-        self.ret: Optional[Union[int, ExitCode]] = None
+        self.calls: list[RecordedHookCall] = []
+        self.ret: int | ExitCode | None = None
 
         def before(hook_name: str, hook_impls, kwargs) -> None:
             self.calls.append(RecordedHookCall(hook_name, kwargs))
@@ -271,17 +275,18 @@ def after(outcome, hook_name: str, hook_impls, kwargs) -> None:
     def finish_recording(self) -> None:
         self._undo_wrapping()
 
-    def getcalls(self, names: Union[str, Iterable[str]]) -> List[RecordedHookCall]:
+    def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]:
         """Get all recorded calls to hooks with the given names (or name)."""
         if isinstance(names, str):
             names = names.split()
         return [call for call in self.calls if call._name in names]
 
-    def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None:
+    def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None:
         __tracebackhide__ = True
         i = 0
         entries = list(entries)
-        backlocals = sys._getframe(1).f_locals
+        # Since Python 3.13, f_locals is not a dict, but eval requires a dict.
+        backlocals = dict(sys._getframe(1).f_locals)
         while entries:
             name, check = entries.pop(0)
             for ind, call in enumerate(self.calls[i:]):
@@ -305,7 +310,7 @@ def popcall(self, name: str) -> RecordedHookCall:
                 del self.calls[i]
                 return call
         lines = [f"could not find call {name!r}, in:"]
-        lines.extend(["  %s" % x for x in self.calls])
+        lines.extend([f"  {x}" for x in self.calls])
         fail("\n".join(lines))
 
     def getcall(self, name: str) -> RecordedHookCall:
@@ -318,45 +323,42 @@ def getcall(self, name: str) -> RecordedHookCall:
     @overload
     def getreports(
         self,
-        names: "Literal['pytest_collectreport']",
-    ) -> Sequence[CollectReport]:
-        ...
+        names: Literal["pytest_collectreport"],
+    ) -> Sequence[CollectReport]: ...
 
     @overload
     def getreports(
         self,
-        names: "Literal['pytest_runtest_logreport']",
-    ) -> Sequence[TestReport]:
-        ...
+        names: Literal["pytest_runtest_logreport"],
+    ) -> Sequence[TestReport]: ...
 
     @overload
     def getreports(
         self,
-        names: Union[str, Iterable[str]] = (
+        names: str | Iterable[str] = (
             "pytest_collectreport",
             "pytest_runtest_logreport",
         ),
-    ) -> Sequence[Union[CollectReport, TestReport]]:
-        ...
+    ) -> Sequence[CollectReport | TestReport]: ...
 
     def getreports(
         self,
-        names: Union[str, Iterable[str]] = (
+        names: str | Iterable[str] = (
             "pytest_collectreport",
             "pytest_runtest_logreport",
         ),
-    ) -> Sequence[Union[CollectReport, TestReport]]:
+    ) -> Sequence[CollectReport | TestReport]:
         return [x.report for x in self.getcalls(names)]
 
     def matchreport(
         self,
         inamepart: str = "",
-        names: Union[str, Iterable[str]] = (
+        names: str | Iterable[str] = (
             "pytest_runtest_logreport",
             "pytest_collectreport",
         ),
-        when: Optional[str] = None,
-    ) -> Union[CollectReport, TestReport]:
+        when: str | None = None,
+    ) -> CollectReport | TestReport:
         """Return a testreport whose dotted import path matches."""
         values = []
         for rep in self.getreports(names=names):
@@ -369,48 +371,43 @@ def matchreport(
                 values.append(rep)
         if not values:
             raise ValueError(
-                "could not find test report matching %r: "
-                "no test reports at all!" % (inamepart,)
+                f"could not find test report matching {inamepart!r}: "
+                "no test reports at all!"
             )
         if len(values) > 1:
             raise ValueError(
-                "found 2 or more testreports matching {!r}: {}".format(
-                    inamepart, values
-                )
+                f"found 2 or more testreports matching {inamepart!r}: {values}"
             )
         return values[0]
 
     @overload
     def getfailures(
         self,
-        names: "Literal['pytest_collectreport']",
-    ) -> Sequence[CollectReport]:
-        ...
+        names: Literal["pytest_collectreport"],
+    ) -> Sequence[CollectReport]: ...
 
     @overload
     def getfailures(
         self,
-        names: "Literal['pytest_runtest_logreport']",
-    ) -> Sequence[TestReport]:
-        ...
+        names: Literal["pytest_runtest_logreport"],
+    ) -> Sequence[TestReport]: ...
 
     @overload
     def getfailures(
         self,
-        names: Union[str, Iterable[str]] = (
+        names: str | Iterable[str] = (
             "pytest_collectreport",
             "pytest_runtest_logreport",
         ),
-    ) -> Sequence[Union[CollectReport, TestReport]]:
-        ...
+    ) -> Sequence[CollectReport | TestReport]: ...
 
     def getfailures(
         self,
-        names: Union[str, Iterable[str]] = (
+        names: str | Iterable[str] = (
             "pytest_collectreport",
             "pytest_runtest_logreport",
         ),
-    ) -> Sequence[Union[CollectReport, TestReport]]:
+    ) -> Sequence[CollectReport | TestReport]:
         return [rep for rep in self.getreports(names) if rep.failed]
 
     def getfailedcollections(self) -> Sequence[CollectReport]:
@@ -418,10 +415,10 @@ def getfailedcollections(self) -> Sequence[CollectReport]:
 
     def listoutcomes(
         self,
-    ) -> Tuple[
+    ) -> tuple[
         Sequence[TestReport],
-        Sequence[Union[CollectReport, TestReport]],
-        Sequence[Union[CollectReport, TestReport]],
+        Sequence[CollectReport | TestReport],
+        Sequence[CollectReport | TestReport],
     ]:
         passed = []
         skipped = []
@@ -440,7 +437,7 @@ def listoutcomes(
                 failed.append(rep)
         return passed, skipped, failed
 
-    def countoutcomes(self) -> List[int]:
+    def countoutcomes(self) -> list[int]:
         return [len(x) for x in self.listoutcomes()]
 
     def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
@@ -460,14 +457,14 @@ def clear(self) -> None:
 
 
 @fixture
-def linecomp() -> "LineComp":
+def linecomp() -> LineComp:
     """A :class: `LineComp` instance for checking that an input linearly
     contains a sequence of strings."""
     return LineComp()
 
 
 @fixture(name="LineMatcher")
-def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
+def LineMatcher_fixture(request: FixtureRequest) -> type[LineMatcher]:
     """A reference to the :class: `LineMatcher`.
 
     This is instantiable with a list of lines (without their trailing newlines).
@@ -477,7 +474,9 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
 
 
 @fixture
-def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pytester":
+def pytester(
+    request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch
+) -> Pytester:
     """
     Facilities to write tests/configuration files, execute pytest in isolation, and match
     against expected output, perfect for black-box testing of pytest plugins.
@@ -488,11 +487,11 @@ def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pyt
     It is particularly useful for testing plugins. It is similar to the :fixture:`tmp_path`
     fixture but provides methods which aid in testing pytest itself.
     """
-    return Pytester(request, tmp_path_factory, _ispytest=True)
+    return Pytester(request, tmp_path_factory, monkeypatch, _ispytest=True)
 
 
 @fixture
-def _sys_snapshot() -> Generator[None, None, None]:
+def _sys_snapshot() -> Generator[None]:
     snappaths = SysPathsSnapshot()
     snapmods = SysModulesSnapshot()
     yield
@@ -501,7 +500,7 @@ def _sys_snapshot() -> Generator[None, None, None]:
 
 
 @fixture
-def _config_for_test() -> Generator[Config, None, None]:
+def _config_for_test() -> Generator[Config]:
     from _pytest.config import get_config
 
     config = get_config()
@@ -521,13 +520,13 @@ class RunResult:
 
     def __init__(
         self,
-        ret: Union[int, ExitCode],
-        outlines: List[str],
-        errlines: List[str],
+        ret: int | ExitCode,
+        outlines: list[str],
+        errlines: list[str],
         duration: float,
     ) -> None:
         try:
-            self.ret: Union[int, ExitCode] = ExitCode(ret)
+            self.ret: int | ExitCode = ExitCode(ret)
             """The return value."""
         except ValueError:
             self.ret = ret
@@ -548,11 +547,13 @@ def __init__(
 
     def __repr__(self) -> str:
         return (
-            "<RunResult ret=%s len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>"
-            % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration)
+            f"<RunResult ret={self.ret!s} "
+            f"len(stdout.lines)={len(self.stdout.lines)} "
+            f"len(stderr.lines)={len(self.stderr.lines)} "
+            f"duration={self.duration:.2f}s>"
         )
 
-    def parseoutcomes(self) -> Dict[str, int]:
+    def parseoutcomes(self) -> dict[str, int]:
         """Return a dictionary of outcome noun -> count from parsing the terminal
         output that the test process produced.
 
@@ -565,7 +566,7 @@ def parseoutcomes(self) -> Dict[str, int]:
         return self.parse_summary_nouns(self.outlines)
 
     @classmethod
-    def parse_summary_nouns(cls, lines) -> Dict[str, int]:
+    def parse_summary_nouns(cls, lines) -> dict[str, int]:
         """Extract the nouns from a pytest terminal summary line.
 
         It always returns the plural noun for consistency::
@@ -596,11 +597,15 @@ def assert_outcomes(
         errors: int = 0,
         xpassed: int = 0,
         xfailed: int = 0,
-        warnings: int = 0,
-        deselected: int = 0,
+        warnings: int | None = None,
+        deselected: int | None = None,
     ) -> None:
-        """Assert that the specified outcomes appear with the respective
-        numbers (0 means it didn't occur) in the text output from a test run."""
+        """
+        Assert that the specified outcomes appear with the respective
+        numbers (0 means it didn't occur) in the text output from a test run.
+
+        ``warnings`` and ``deselected`` are only checked if not None.
+        """
         __tracebackhide__ = True
         from _pytest.pytester_assertions import assert_outcomes
 
@@ -618,16 +623,8 @@ def assert_outcomes(
         )
 
 
-class CwdSnapshot:
-    def __init__(self) -> None:
-        self.__saved = os.getcwd()
-
-    def restore(self) -> None:
-        os.chdir(self.__saved)
-
-
 class SysModulesSnapshot:
-    def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None:
+    def __init__(self, preserve: Callable[[str], bool] | None = None) -> None:
         self.__preserve = preserve
         self.__saved = dict(sys.modules)
 
@@ -655,22 +652,12 @@ class Pytester:
     against expected output, perfect for black-box testing of pytest plugins.
 
     It attempts to isolate the test run from external factors as much as possible, modifying
-    the current working directory to ``path`` and environment variables during initialization.
-
-    Attributes:
-
-    :ivar Path path: temporary directory path used to create files/run tests from, etc.
-
-    :ivar plugins:
-       A list of plugins to use with :py:meth:`parseconfig` and
-       :py:meth:`runpytest`.  Initially this is an empty list but plugins can
-       be added to the list.  The type of items to add to the list depends on
-       the method using them so refer to them for details.
+    the current working directory to :attr:`path` and environment variables during initialization.
     """
 
     __test__ = False
 
-    CLOSE_STDIN: "Final" = NOTSET
+    CLOSE_STDIN: Final = NOTSET
 
     class TimeoutExpired(Exception):
         pass
@@ -679,30 +666,34 @@ def __init__(
         self,
         request: FixtureRequest,
         tmp_path_factory: TempPathFactory,
+        monkeypatch: MonkeyPatch,
         *,
         _ispytest: bool = False,
     ) -> None:
         check_ispytest(_ispytest)
         self._request = request
-        self._mod_collections: WeakKeyDictionary[
-            Collector, List[Union[Item, Collector]]
-        ] = WeakKeyDictionary()
+        self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = (
+            WeakKeyDictionary()
+        )
         if request.function:
             name: str = request.function.__name__
         else:
             name = request.node.name
         self._name = name
         self._path: Path = tmp_path_factory.mktemp(name, numbered=True)
-        self.plugins: List[Union[str, _PluggyPlugin]] = []
-        self._cwd_snapshot = CwdSnapshot()
+        #: A list of plugins to use with :py:meth:`parseconfig` and
+        #: :py:meth:`runpytest`.  Initially this is an empty list but plugins can
+        #: be added to the list.  The type of items to add to the list depends on
+        #: the method using them so refer to them for details.
+        self.plugins: list[str | _PluggyPlugin] = []
         self._sys_path_snapshot = SysPathsSnapshot()
         self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
-        self.chdir()
         self._request.addfinalizer(self._finalize)
         self._method = self._request.config.getoption("--runpytest")
         self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True)
 
-        self._monkeypatch = mp = MonkeyPatch()
+        self._monkeypatch = mp = monkeypatch
+        self.chdir()
         mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot))
         # Ensure no unexpected caching via tox.
         mp.delenv("TOX_ENV_DIR", raising=False)
@@ -717,7 +708,7 @@ def __init__(
 
     @property
     def path(self) -> Path:
-        """Temporary directory where files are created and pytest is executed."""
+        """Temporary directory path used to create files/run tests from, etc."""
         return self._path
 
     def __repr__(self) -> str:
@@ -733,8 +724,6 @@ def _finalize(self) -> None:
         """
         self._sys_modules_snapshot.restore()
         self._sys_path_snapshot.restore()
-        self._cwd_snapshot.restore()
-        self._monkeypatch.undo()
 
     def __take_sys_modules_snapshot(self) -> SysModulesSnapshot:
         # Some zope modules used by twisted-related tests keep internal state
@@ -749,8 +738,8 @@ def preserve_module(name):
         return SysModulesSnapshot(preserve=preserve_module)
 
     def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder:
-        """Create a new :py:class:`HookRecorder` for a PluginManager."""
-        pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True)
+        """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`."""
+        pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True)  # type: ignore[attr-defined]
         self._request.addfinalizer(reprec.finish_recording)
         return reprec
 
@@ -759,23 +748,26 @@ def chdir(self) -> None:
 
         This is done automatically upon instantiation.
         """
-        os.chdir(self.path)
+        self._monkeypatch.chdir(self.path)
 
     def _makefile(
         self,
         ext: str,
-        lines: Sequence[Union[Any, bytes]],
-        files: Dict[str, str],
+        lines: Sequence[Any | bytes],
+        files: dict[str, str],
         encoding: str = "utf-8",
     ) -> Path:
         items = list(files.items())
 
+        if ext is None:
+            raise TypeError("ext must not be None")
+
         if ext and not ext.startswith("."):
             raise ValueError(
                 f"pytester.makefile expects a file extension, try .{ext} instead of {ext}"
             )
 
-        def to_text(s: Union[Any, bytes]) -> str:
+        def to_text(s: Any | bytes) -> str:
             return s.decode(encoding) if isinstance(s, bytes) else str(s)
 
         if lines:
@@ -798,7 +790,7 @@ def to_text(s: Union[Any, bytes]) -> str:
     def makefile(self, ext: str, *args: str, **kwargs: str) -> Path:
         r"""Create new text file(s) in the test directory.
 
-        :param str ext:
+        :param ext:
             The extension the file(s) should use, including the dot, e.g. `.py`.
         :param args:
             All args are treated as strings and joined using newlines.
@@ -807,6 +799,8 @@ def makefile(self, ext: str, *args: str, **kwargs: str) -> Path:
         :param kwargs:
             Each keyword is the name of a file, while the value of it will
             be written as contents of the file.
+        :returns:
+            The first created file.
 
         Examples:
 
@@ -826,11 +820,19 @@ def makefile(self, ext: str, *args: str, **kwargs: str) -> Path:
         return self._makefile(ext, args, kwargs)
 
     def makeconftest(self, source: str) -> Path:
-        """Write a contest.py file with 'source' as contents."""
+        """Write a conftest.py file.
+
+        :param source: The contents.
+        :returns: The conftest.py file.
+        """
         return self.makepyfile(conftest=source)
 
     def makeini(self, source: str) -> Path:
-        """Write a tox.ini file with 'source' as contents."""
+        """Write a tox.ini file.
+
+        :param source: The contents.
+        :returns: The tox.ini file.
+        """
         return self.makefile(".ini", tox=source)
 
     def getinicfg(self, source: str) -> SectionWrapper:
@@ -839,7 +841,10 @@ def getinicfg(self, source: str) -> SectionWrapper:
         return IniConfig(str(p))["pytest"]
 
     def makepyprojecttoml(self, source: str) -> Path:
-        """Write a pyproject.toml file with 'source' as contents.
+        """Write a pyproject.toml file.
+
+        :param source: The contents.
+        :returns: The pyproject.ini file.
 
         .. versionadded:: 6.0
         """
@@ -885,26 +890,34 @@ def test_something(pytester):
         """
         return self._makefile(".txt", args, kwargs)
 
-    def syspathinsert(
-        self, path: Optional[Union[str, "os.PathLike[str]"]] = None
-    ) -> None:
+    def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None:
         """Prepend a directory to sys.path, defaults to :attr:`path`.
 
         This is undone automatically when this object dies at the end of each
         test.
+
+        :param path:
+            The path.
         """
         if path is None:
             path = self.path
 
         self._monkeypatch.syspath_prepend(str(path))
 
-    def mkdir(self, name: str) -> Path:
-        """Create a new (sub)directory."""
+    def mkdir(self, name: str | os.PathLike[str]) -> Path:
+        """Create a new (sub)directory.
+
+        :param name:
+            The name of the directory, relative to the pytester path.
+        :returns:
+            The created directory.
+        :rtype: pathlib.Path
+        """
         p = self.path / name
         p.mkdir()
         return p
 
-    def mkpydir(self, name: str) -> Path:
+    def mkpydir(self, name: str | os.PathLike[str]) -> Path:
         """Create a new python package.
 
         This creates a (sub)directory with an empty ``__init__.py`` file so it
@@ -915,17 +928,19 @@ def mkpydir(self, name: str) -> Path:
         p.joinpath("__init__.py").touch()
         return p
 
-    def copy_example(self, name: Optional[str] = None) -> Path:
+    def copy_example(self, name: str | None = None) -> Path:
         """Copy file from project's directory into the testdir.
 
-        :param str name: The name of the file to copy.
-        :return: path to the copied directory (inside ``self.path``).
-
+        :param name:
+            The name of the file to copy.
+        :return:
+            Path to the copied directory (inside ``self.path``).
+        :rtype: pathlib.Path
         """
-        example_dir = self._request.config.getini("pytester_example_dir")
-        if example_dir is None:
+        example_dir_ = self._request.config.getini("pytester_example_dir")
+        if example_dir_ is None:
             raise ValueError("pytester_example_dir is unset, can't copy examples")
-        example_dir = self._request.config.rootpath / example_dir
+        example_dir: Path = self._request.config.rootpath / example_dir_
 
         for extra_element in self._request.node.iter_markers("pytester_example_path"):
             assert extra_element.args
@@ -948,7 +963,7 @@ def copy_example(self, name: Optional[str] = None) -> Path:
             example_path = example_dir.joinpath(name)
 
         if example_path.is_dir() and not example_path.joinpath("__init__.py").is_file():
-            copytree(example_path, self.path)
+            shutil.copytree(example_path, self.path, symlinks=True, dirs_exist_ok=True)
             return self.path
         elif example_path.is_file():
             result = self.path.joinpath(example_path.name)
@@ -959,16 +974,16 @@ def copy_example(self, name: Optional[str] = None) -> Path:
                 f'example "{example_path}" is not found as a file or directory'
             )
 
-    def getnode(
-        self, config: Config, arg: Union[str, "os.PathLike[str]"]
-    ) -> Optional[Union[Collector, Item]]:
-        """Return the collection node of a file.
+    def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item:
+        """Get the collection node of a file.
 
-        :param pytest.Config config:
+        :param config:
            A pytest config.
            See :py:meth:`parseconfig` and :py:meth:`parseconfigure` for creating it.
-        :param os.PathLike[str] arg:
+        :param arg:
             Path to the file.
+        :returns:
+            The node.
         """
         session = Session.from_config(config)
         assert "::" not in str(arg)
@@ -978,13 +993,16 @@ def getnode(
         config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
         return res
 
-    def getpathnode(self, path: Union[str, "os.PathLike[str]"]):
+    def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item:
         """Return the collection node of a file.
 
         This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to
         create the (configured) pytest Config instance.
 
-        :param os.PathLike[str] path: Path to the file.
+        :param path:
+            Path to the file.
+        :returns:
+            The node.
         """
         path = Path(path)
         config = self.parseconfigure(path)
@@ -995,14 +1013,19 @@ def getpathnode(self, path: Union[str, "os.PathLike[str]"]):
         config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK)
         return res
 
-    def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]:
+    def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]:
         """Generate all test items from a collection node.
 
         This recurses into the collection node and returns a list of all the
         test items contained within.
+
+        :param colitems:
+            The collection nodes.
+        :returns:
+            The collected items.
         """
         session = colitems[0].session
-        result: List[Item] = []
+        result: list[Item] = []
         for colitem in colitems:
             result.extend(session.genitems(colitem))
         return result
@@ -1013,7 +1036,7 @@ def runitem(self, source: str) -> Any:
         The calling test instance (class containing the test method) must
         provide a ``.getrunner()`` method which should return a runner which
         can run the test protocol for a single item, e.g.
-        :py:func:`_pytest.runner.runtestprotocol`.
+        ``_pytest.runner.runtestprotocol``.
         """
         # used from runner functional tests
         item = self.getitem(source)
@@ -1033,11 +1056,11 @@ def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder:
         :param cmdlineargs: Any extra command line arguments to use.
         """
         p = self.makepyfile(source)
-        values = list(cmdlineargs) + [p]
+        values = [*list(cmdlineargs), p]
         return self.inline_run(*values)
 
-    def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]:
-        """Run ``pytest.main(['--collectonly'])`` in-process.
+    def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]:
+        """Run ``pytest.main(['--collect-only'])`` in-process.
 
         Runs the :py:func:`pytest.main` function to run all of pytest inside
         the test process itself like :py:meth:`inline_run`, but returns a
@@ -1049,7 +1072,7 @@ def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]:
 
     def inline_run(
         self,
-        *args: Union[str, "os.PathLike[str]"],
+        *args: str | os.PathLike[str],
         plugins=(),
         no_reraise_ctrlc: bool = False,
     ) -> HookRecorder:
@@ -1119,7 +1142,7 @@ class reprec:  # type: ignore
                 finalizer()
 
     def runpytest_inprocess(
-        self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any
+        self, *args: str | os.PathLike[str], **kwargs: Any
     ) -> RunResult:
         """Return result of running pytest in-process, providing a similar
         interface to what self.runpytest() provides."""
@@ -1162,9 +1185,7 @@ class reprec:  # type: ignore
         res.reprec = reprec  # type: ignore
         return res
 
-    def runpytest(
-        self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any
-    ) -> RunResult:
+    def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult:
         """Run pytest inline or in a subprocess, depending on the command line
         option "--runpytest" and return a :py:class:`~pytest.RunResult`."""
         new_args = self._ensure_basetemp(args)
@@ -1175,26 +1196,29 @@ def runpytest(
         raise RuntimeError(f"Unrecognized runpytest option: {self._method}")
 
     def _ensure_basetemp(
-        self, args: Sequence[Union[str, "os.PathLike[str]"]]
-    ) -> List[Union[str, "os.PathLike[str]"]]:
+        self, args: Sequence[str | os.PathLike[str]]
+    ) -> list[str | os.PathLike[str]]:
         new_args = list(args)
         for x in new_args:
             if str(x).startswith("--basetemp"):
                 break
         else:
-            new_args.append("--basetemp=%s" % self.path.parent.joinpath("basetemp"))
+            new_args.append(
+                "--basetemp={}".format(self.path.parent.joinpath("basetemp"))
+            )
         return new_args
 
-    def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config:
-        """Return a new pytest Config instance from given commandline args.
+    def parseconfig(self, *args: str | os.PathLike[str]) -> Config:
+        """Return a new pytest :class:`pytest.Config` instance from given
+        commandline args.
 
-        This invokes the pytest bootstrapping code in _pytest.config to create
-        a new :py:class:`_pytest.core.PluginManager` and call the
-        pytest_cmdline_parse hook to create a new
-        :py:class:`pytest.Config` instance.
+        This invokes the pytest bootstrapping code in _pytest.config to create a
+        new :py:class:`pytest.PytestPluginManager` and call the
+        :hook:`pytest_cmdline_parse` hook to create a new :class:`pytest.Config`
+        instance.
 
-        If :py:attr:`plugins` has been populated they should be plugin modules
-        to be registered with the PluginManager.
+        If :attr:`plugins` has been populated they should be plugin modules
+        to be registered with the plugin manager.
         """
         import _pytest.config
 
@@ -1208,18 +1232,19 @@ def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config:
         self._request.addfinalizer(config._ensure_unconfigure)
         return config
 
-    def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config:
+    def parseconfigure(self, *args: str | os.PathLike[str]) -> Config:
         """Return a new pytest configured Config instance.
 
         Returns a new :py:class:`pytest.Config` instance like
-        :py:meth:`parseconfig`, but also calls the pytest_configure hook.
+        :py:meth:`parseconfig`, but also calls the :hook:`pytest_configure`
+        hook.
         """
         config = self.parseconfig(*args)
         config._do_configure()
         return config
 
     def getitem(
-        self, source: Union[str, "os.PathLike[str]"], funcname: str = "test_func"
+        self, source: str | os.PathLike[str], funcname: str = "test_func"
     ) -> Item:
         """Return the test item for a test function.
 
@@ -1231,16 +1256,16 @@ def getitem(
             The module source.
         :param funcname:
             The name of the test function for which to return a test item.
+        :returns:
+            The test item.
         """
         items = self.getitems(source)
         for item in items:
             if item.name == funcname:
                 return item
-        assert 0, "{!r} item not found in module:\n{}\nitems: {}".format(
-            funcname, source, items
-        )
+        assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}"
 
-    def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]:
+    def getitems(self, source: str | os.PathLike[str]) -> list[Item]:
         """Return all test items collected from the module.
 
         Writes the source to a Python file and runs pytest's collection on
@@ -1251,7 +1276,7 @@ def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]:
 
     def getmodulecol(
         self,
-        source: Union[str, "os.PathLike[str]"],
+        source: str | os.PathLike[str],
         configargs=(),
         *,
         withinit: bool = False,
@@ -1283,12 +1308,10 @@ def getmodulecol(
         self.config = config = self.parseconfigure(path, *configargs)
         return self.getnode(config, path)
 
-    def collect_by_name(
-        self, modcol: Collector, name: str
-    ) -> Optional[Union[Item, Collector]]:
+    def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None:
         """Return the collection node for name from the module collection.
 
-        Searchs a module collection node for a collection node matching the
+        Searches a module collection node for a collection node matching the
         given name.
 
         :param modcol: A module collection node; see :py:meth:`getmodulecol`.
@@ -1303,10 +1326,10 @@ def collect_by_name(
 
     def popen(
         self,
-        cmdargs: Sequence[Union[str, "os.PathLike[str]"]],
-        stdout: Union[int, TextIO] = subprocess.PIPE,
-        stderr: Union[int, TextIO] = subprocess.PIPE,
-        stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN,
+        cmdargs: Sequence[str | os.PathLike[str]],
+        stdout: int | TextIO = subprocess.PIPE,
+        stderr: int | TextIO = subprocess.PIPE,
+        stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN,
         **kw,
     ):
         """Invoke :py:class:`subprocess.Popen`.
@@ -1341,9 +1364,9 @@ def popen(
 
     def run(
         self,
-        *cmdargs: Union[str, "os.PathLike[str]"],
-        timeout: Optional[float] = None,
-        stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN,
+        *cmdargs: str | os.PathLike[str],
+        timeout: float | None = None,
+        stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN,
     ) -> RunResult:
         """Run a command with arguments.
 
@@ -1360,7 +1383,7 @@ def run(
         :param stdin:
             Optional standard input.
 
-            - If it is :py:attr:`CLOSE_STDIN` (Default), then this method calls
+            - If it is ``CLOSE_STDIN`` (Default), then this method calls
               :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and
               the standard input is closed immediately after the new command is
               started.
@@ -1371,6 +1394,10 @@ def run(
             - Otherwise, it is passed through to :py:class:`subprocess.Popen`.
               For further information in this case, consult the document of the
               ``stdin`` parameter in :py:class:`subprocess.Popen`.
+        :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int
+        :returns:
+            The result.
+
         """
         __tracebackhide__ = True
 
@@ -1395,10 +1422,7 @@ def run(
             def handle_timeout() -> None:
                 __tracebackhide__ = True
 
-                timeout_message = (
-                    "{seconds} second timeout expired running:"
-                    " {command}".format(seconds=timeout, command=cmdargs)
-                )
+                timeout_message = f"{timeout} second timeout expired running: {cmdargs}"
 
                 popen.kill()
                 popen.wait()
@@ -1430,10 +1454,10 @@ def _dump_lines(self, lines, fp):
         except UnicodeEncodeError:
             print(f"couldn't print to {fp} because of encoding")
 
-    def _getpytestargs(self) -> Tuple[str, ...]:
+    def _getpytestargs(self) -> tuple[str, ...]:
         return sys.executable, "-mpytest"
 
-    def runpython(self, script: "os.PathLike[str]") -> RunResult:
+    def runpython(self, script: os.PathLike[str]) -> RunResult:
         """Run a python script using sys.executable as interpreter."""
         return self.run(sys.executable, script)
 
@@ -1442,7 +1466,7 @@ def runpython_c(self, command: str) -> RunResult:
         return self.run(sys.executable, "-c", command)
 
     def runpytest_subprocess(
-        self, *args: Union[str, "os.PathLike[str]"], timeout: Optional[float] = None
+        self, *args: str | os.PathLike[str], timeout: float | None = None
     ) -> RunResult:
         """Run pytest as a subprocess with given arguments.
 
@@ -1457,19 +1481,19 @@ def runpytest_subprocess(
         :param timeout:
             The period in seconds after which to timeout and raise
             :py:class:`Pytester.TimeoutExpired`.
+        :returns:
+            The result.
         """
         __tracebackhide__ = True
         p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700)
-        args = ("--basetemp=%s" % p,) + args
+        args = (f"--basetemp={p}", *args)
         plugins = [x for x in self.plugins if isinstance(x, str)]
         if plugins:
-            args = ("-p", plugins[0]) + args
+            args = ("-p", plugins[0], *args)
         args = self._getpytestargs() + args
         return self.run(*args, timeout=timeout)
 
-    def spawn_pytest(
-        self, string: str, expect_timeout: float = 10.0
-    ) -> "pexpect.spawn":
+    def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn:
         """Run pytest using pexpect.
 
         This makes sure to use the right pytest and sets up the temporary
@@ -1483,7 +1507,7 @@ def spawn_pytest(
         cmd = f"{invoke} --basetemp={basetemp} {string}"
         return self.spawn(cmd, expect_timeout=expect_timeout)
 
-    def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
+    def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn:
         """Run a command using pexpect.
 
         The pexpect child is returned.
@@ -1528,9 +1552,9 @@ class LineMatcher:
     ``text.splitlines()``.
     """
 
-    def __init__(self, lines: List[str]) -> None:
+    def __init__(self, lines: list[str]) -> None:
         self.lines = lines
-        self._log_output: List[str] = []
+        self._log_output: list[str] = []
 
     def __str__(self) -> str:
         """Return the entire original text.
@@ -1540,7 +1564,7 @@ def __str__(self) -> str:
         """
         return "\n".join(self.lines)
 
-    def _getlines(self, lines2: Union[str, Sequence[str], Source]) -> Sequence[str]:
+    def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]:
         if isinstance(lines2, str):
             lines2 = Source(lines2)
         if isinstance(lines2, Source):
@@ -1568,7 +1592,7 @@ def _match_lines_random(
                     self._log("matched: ", repr(line))
                     break
             else:
-                msg = "line %r not found in output" % line
+                msg = f"line {line!r} not found in output"
                 self._log(msg)
                 self._fail(msg)
 
@@ -1580,7 +1604,7 @@ def get_lines_after(self, fnline: str) -> Sequence[str]:
         for i, line in enumerate(self.lines):
             if fnline == line or fnmatch(line, fnline):
                 return self.lines[i + 1 :]
-        raise ValueError("line %r not found in output" % fnline)
+        raise ValueError(f"line {fnline!r} not found in output")
 
     def _log(self, *args) -> None:
         self._log_output.append(" ".join(str(x) for x in args))
@@ -1665,7 +1689,7 @@ def _match_lines(
                     started = True
                     break
                 elif match_func(nextline, line):
-                    self._log("%s:" % match_nickname, repr(line))
+                    self._log(f"{match_nickname}:", repr(line))
                     self._log(
                         "{:>{width}}".format("with:", width=wnick), repr(nextline)
                     )
diff --git a/src/_pytest/pytester_assertions.py b/src/_pytest/pytester_assertions.py
index 6a5aabece47..915cc8a10ff 100644
--- a/src/_pytest/pytester_assertions.py
+++ b/src/_pytest/pytester_assertions.py
@@ -1,22 +1,22 @@
 """Helper plugin for pytester; should not be loaded on its own."""
+
 # This plugin contains assertions used by pytester. pytester cannot
 # contain them itself, since it is imported by the `pytest` module,
 # hence cannot be subject to assertion rewriting, which requires a
 # module to not be already imported.
-from typing import Dict
-from typing import Sequence
-from typing import Tuple
-from typing import Union
+from __future__ import annotations
+
+from collections.abc import Sequence
 
 from _pytest.reports import CollectReport
 from _pytest.reports import TestReport
 
 
 def assertoutcome(
-    outcomes: Tuple[
+    outcomes: tuple[
         Sequence[TestReport],
-        Sequence[Union[CollectReport, TestReport]],
-        Sequence[Union[CollectReport, TestReport]],
+        Sequence[CollectReport | TestReport],
+        Sequence[CollectReport | TestReport],
     ],
     passed: int = 0,
     skipped: int = 0,
@@ -35,15 +35,15 @@ def assertoutcome(
 
 
 def assert_outcomes(
-    outcomes: Dict[str, int],
+    outcomes: dict[str, int],
     passed: int = 0,
     skipped: int = 0,
     failed: int = 0,
     errors: int = 0,
     xpassed: int = 0,
     xfailed: int = 0,
-    warnings: int = 0,
-    deselected: int = 0,
+    warnings: int | None = None,
+    deselected: int | None = None,
 ) -> None:
     """Assert that the specified outcomes appear with the respective
     numbers (0 means it didn't occur) in the text output from a test run."""
@@ -56,8 +56,6 @@ def assert_outcomes(
         "errors": outcomes.get("errors", 0),
         "xpassed": outcomes.get("xpassed", 0),
         "xfailed": outcomes.get("xfailed", 0),
-        "warnings": outcomes.get("warnings", 0),
-        "deselected": outcomes.get("deselected", 0),
     }
     expected = {
         "passed": passed,
@@ -66,7 +64,11 @@ def assert_outcomes(
         "errors": errors,
         "xpassed": xpassed,
         "xfailed": xfailed,
-        "warnings": warnings,
-        "deselected": deselected,
     }
+    if warnings is not None:
+        obtained["warnings"] = outcomes.get("warnings", 0)
+        expected["warnings"] = warnings
+    if deselected is not None:
+        obtained["deselected"] = outcomes.get("deselected", 0)
+        expected["deselected"] = deselected
     assert obtained == expected
diff --git a/src/_pytest/python.py b/src/_pytest/python.py
index b557cd8fa77..902bcfade9f 100644
--- a/src/_pytest/python.py
+++ b/src/_pytest/python.py
@@ -1,33 +1,33 @@
+# mypy: allow-untyped-defs
 """Python test discovery, setup and run of test functions."""
+
+from __future__ import annotations
+
+import abc
+from collections import Counter
+from collections import defaultdict
+from collections.abc import Callable
+from collections.abc import Generator
+from collections.abc import Iterable
+from collections.abc import Iterator
+from collections.abc import Mapping
+from collections.abc import Sequence
+import dataclasses
 import enum
 import fnmatch
+from functools import partial
 import inspect
 import itertools
 import os
-import sys
-import types
-import warnings
-from collections import Counter
-from collections import defaultdict
-from functools import partial
 from pathlib import Path
+import re
+import types
 from typing import Any
-from typing import Callable
-from typing import Dict
-from typing import Generator
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Pattern
-from typing import Sequence
-from typing import Set
-from typing import Tuple
+from typing import final
+from typing import Literal
+from typing import NoReturn
 from typing import TYPE_CHECKING
-from typing import Union
-
-import attr
+import warnings
 
 import _pytest
 from _pytest import fixtures
@@ -36,114 +36,79 @@
 from _pytest._code import getfslineno
 from _pytest._code.code import ExceptionInfo
 from _pytest._code.code import TerminalRepr
-from _pytest._io import TerminalWriter
+from _pytest._code.code import Traceback
 from _pytest._io.saferepr import saferepr
 from _pytest.compat import ascii_escaped
-from _pytest.compat import assert_never
-from _pytest.compat import final
 from _pytest.compat import get_default_arg_names
 from _pytest.compat import get_real_func
 from _pytest.compat import getimfunc
-from _pytest.compat import getlocation
 from _pytest.compat import is_async_function
-from _pytest.compat import is_generator
 from _pytest.compat import LEGACY_PATH
 from _pytest.compat import NOTSET
 from _pytest.compat import safe_getattr
 from _pytest.compat import safe_isclass
-from _pytest.compat import STRING_TYPES
 from _pytest.config import Config
-from _pytest.config import ExitCode
 from _pytest.config import hookimpl
 from _pytest.config.argparsing import Parser
 from _pytest.deprecated import check_ispytest
-from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
-from _pytest.deprecated import INSTANCE_COLLECTOR
+from _pytest.fixtures import FixtureDef
+from _pytest.fixtures import FixtureRequest
 from _pytest.fixtures import FuncFixtureInfo
+from _pytest.fixtures import get_scope_node
 from _pytest.main import Session
-from _pytest.mark import MARK_GEN
 from _pytest.mark import ParameterSet
+from _pytest.mark.structures import _HiddenParam
 from _pytest.mark.structures import get_unpacked_marks
+from _pytest.mark.structures import HIDDEN_PARAM
 from _pytest.mark.structures import Mark
 from _pytest.mark.structures import MarkDecorator
 from _pytest.mark.structures import normalize_mark_list
 from _pytest.outcomes import fail
 from _pytest.outcomes import skip
-from _pytest.pathlib import bestrelpath
 from _pytest.pathlib import fnmatch_ex
 from _pytest.pathlib import import_path
 from _pytest.pathlib import ImportPathMismatchError
-from _pytest.pathlib import parts
-from _pytest.pathlib import visit
+from _pytest.pathlib import scandir
+from _pytest.scope import _ScopeName
 from _pytest.scope import Scope
+from _pytest.stash import StashKey
 from _pytest.warning_types import PytestCollectionWarning
-from _pytest.warning_types import PytestUnhandledCoroutineWarning
-
-if TYPE_CHECKING:
-    from typing_extensions import Literal
-    from _pytest.scope import _ScopeName
 
 
-_PYTEST_DIR = Path(_pytest.__file__).parent
+if TYPE_CHECKING:
+    from typing_extensions import Self
 
 
 def pytest_addoption(parser: Parser) -> None:
-    group = parser.getgroup("general")
-    group.addoption(
-        "--fixtures",
-        "--funcargs",
-        action="store_true",
-        dest="showfixtures",
-        default=False,
-        help="show available fixtures, sorted by plugin appearance "
-        "(fixtures with leading '_' are only shown with '-v')",
-    )
-    group.addoption(
-        "--fixtures-per-test",
-        action="store_true",
-        dest="show_fixtures_per_test",
-        default=False,
-        help="show fixtures per test",
-    )
     parser.addini(
         "python_files",
         type="args",
         # NOTE: default is also used in AssertionRewritingHook.
         default=["test_*.py", "*_test.py"],
-        help="glob-style file patterns for Python test module discovery",
+        help="Glob-style file patterns for Python test module discovery",
     )
     parser.addini(
         "python_classes",
         type="args",
         default=["Test"],
-        help="prefixes or glob names for Python test class discovery",
+        help="Prefixes or glob names for Python test class discovery",
     )
     parser.addini(
         "python_functions",
         type="args",
         default=["test"],
-        help="prefixes or glob names for Python test function and method discovery",
+        help="Prefixes or glob names for Python test function and method discovery",
     )
     parser.addini(
         "disable_test_id_escaping_and_forfeit_all_rights_to_community_support",
         type="bool",
         default=False,
-        help="disable string escape non-ascii characters, might cause unwanted "
+        help="Disable string escape non-ASCII characters, might cause unwanted "
         "side effects(use at your own risk)",
     )
 
 
-def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
-    if config.option.showfixtures:
-        showfixtures(config)
-        return 0
-    if config.option.show_fixtures_per_test:
-        show_fixtures_per_test(config)
-        return 0
-    return None
-
-
-def pytest_generate_tests(metafunc: "Metafunc") -> None:
+def pytest_generate_tests(metafunc: Metafunc) -> None:
     for marker in metafunc.definition.iter_markers(name="parametrize"):
         metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker)
 
@@ -168,38 +133,59 @@ def pytest_configure(config: Config) -> None:
     )
 
 
-def async_warn_and_skip(nodeid: str) -> None:
-    msg = "async def functions are not natively supported and have been skipped.\n"
-    msg += (
+def async_fail(nodeid: str) -> None:
+    msg = (
+        "async def functions are not natively supported.\n"
         "You need to install a suitable plugin for your async framework, for example:\n"
+        "  - anyio\n"
+        "  - pytest-asyncio\n"
+        "  - pytest-tornasync\n"
+        "  - pytest-trio\n"
+        "  - pytest-twisted"
     )
-    msg += "  - anyio\n"
-    msg += "  - pytest-asyncio\n"
-    msg += "  - pytest-tornasync\n"
-    msg += "  - pytest-trio\n"
-    msg += "  - pytest-twisted"
-    warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid)))
-    skip(reason="async def function and no async plugin installed (see warnings)")
+    fail(msg, pytrace=False)
 
 
 @hookimpl(trylast=True)
-def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
+def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
     testfunction = pyfuncitem.obj
     if is_async_function(testfunction):
-        async_warn_and_skip(pyfuncitem.nodeid)
+        async_fail(pyfuncitem.nodeid)
     funcargs = pyfuncitem.funcargs
     testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
     result = testfunction(**testargs)
     if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
-        async_warn_and_skip(pyfuncitem.nodeid)
+        async_fail(pyfuncitem.nodeid)
+    elif result is not None:
+        fail(
+            (
+                f"Expected None, but test returned {result!r}. "
+                "Did you mean to use `assert` instead of `return`?"
+            ),
+            pytrace=False,
+        )
     return True
 
 
-def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]:
+def pytest_collect_directory(
+    path: Path, parent: nodes.Collector
+) -> nodes.Collector | None:
+    pkginit = path / "__init__.py"
+    try:
+        has_pkginit = pkginit.is_file()
+    except PermissionError:
+        # See https://github.com/pytest-dev/pytest/issues/12120#issuecomment-2106349096.
+        return None
+    if has_pkginit:
+        return Package.from_parent(parent, path=path)
+    return None
+
+
+def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Module | None:
     if file_path.suffix == ".py":
         if not parent.session.isinitpath(file_path):
             if not path_matches_patterns(
-                file_path, parent.config.getini("python_files") + ["__init__.py"]
+                file_path, parent.config.getini("python_files")
             ):
                 return None
         ihook = parent.session.gethookproxy(file_path)
@@ -215,16 +201,15 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool:
     return any(fnmatch_ex(pattern, path) for pattern in patterns)
 
 
-def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module":
-    if module_path.name == "__init__.py":
-        pkg: Package = Package.from_parent(parent, path=module_path)
-        return pkg
-    mod: Module = Module.from_parent(parent, path=module_path)
-    return mod
+def pytest_pycollect_makemodule(module_path: Path, parent) -> Module:
+    return Module.from_parent(parent, path=module_path)
 
 
 @hookimpl(trylast=True)
-def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
+def pytest_pycollect_makeitem(
+    collector: Module | Class, name: str, obj: object
+) -> None | nodes.Item | nodes.Collector | list[nodes.Item | nodes.Collector]:
+    assert isinstance(collector, (Class, Module)), type(collector)
     # Nothing was collected elsewhere, let's do it here.
     if safe_isclass(obj):
         if collector.istestclass(obj, name):
@@ -239,23 +224,21 @@ def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
             filename, lineno = getfslineno(obj)
             warnings.warn_explicit(
                 message=PytestCollectionWarning(
-                    "cannot collect %r because it is not a function." % name
+                    f"cannot collect {name!r} because it is not a function."
                 ),
                 category=None,
                 filename=str(filename),
                 lineno=lineno + 1,
             )
         elif getattr(obj, "__test__", True):
-            if is_generator(obj):
-                res = Function.from_parent(collector, name=name)
-                reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
-                    name=name
+            if inspect.isgeneratorfunction(obj):
+                fail(
+                    f"'yield' keyword is allowed in fixtures, but not in tests ({name})",
+                    pytrace=False,
                 )
-                res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
-                res.warn(PytestCollectionWarning(reason))
-            else:
-                res = list(collector._genfunctions(name, obj))
-            return res
+            return list(collector._genfunctions(name, obj))
+        return None
+    return None
 
 
 class PyobjMixin(nodes.Node):
@@ -278,6 +261,16 @@ def cls(self):
         node = self.getparent(Class)
         return node.obj if node is not None else None
 
+    @property
+    def instance(self):
+        """Python instance object the function is bound to.
+
+        Returns None if not a test method, e.g. for a standalone test function,
+        a class or a module.
+        """
+        # Overridden by Function.
+        return None
+
     @property
     def obj(self):
         """Underlying Python object."""
@@ -288,6 +281,9 @@ def obj(self):
             # used to avoid Function marker duplication
             if self._ALLOW_MARKERS:
                 self.own_markers.extend(get_unpacked_marks(self.obj))
+                # This assumes that `obj` is called before there is a chance
+                # to add custom keys to `self.keywords`, so no fear of overriding.
+                self.keywords.update((mark.name, mark) for mark in self.own_markers)
         return obj
 
     @obj.setter
@@ -303,10 +299,8 @@ def _getobj(self):
 
     def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str:
         """Return Python path relative to the containing module."""
-        chain = self.listchain()
-        chain.reverse()
         parts = []
-        for node in chain:
+        for node in self.iter_parents():
             name = node.name
             if isinstance(node, Module):
                 name = os.path.splitext(name)[0]
@@ -318,21 +312,10 @@ def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) ->
         parts.reverse()
         return ".".join(parts)
 
-    def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
+    def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]:
         # XXX caching?
-        obj = self.obj
-        compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
-        if isinstance(compat_co_firstlineno, int):
-            # nose compatibility
-            file_path = sys.modules[obj.__module__].__file__
-            if file_path.endswith(".pyc"):
-                file_path = file_path[:-1]
-            path: Union["os.PathLike[str]", str] = file_path
-            lineno = compat_co_firstlineno
-        else:
-            path, lineno = getfslineno(obj)
+        path, lineno = getfslineno(self.obj)
         modpath = self.getmodpath()
-        assert isinstance(lineno, int)
         return path, lineno, modpath
 
 
@@ -341,7 +324,7 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str
 # hook is not called for them.
 # fmt: off
 class _EmptyClass: pass  # noqa: E701
-IGNORED_ATTRIBUTES = frozenset.union(  # noqa: E305
+IGNORED_ATTRIBUTES = frozenset.union(
     frozenset(),
     # Module.
     dir(types.ModuleType("empty_module")),
@@ -356,7 +339,7 @@ class _EmptyClass: pass  # noqa: E701
 # fmt: on
 
 
-class PyCollector(PyobjMixin, nodes.Collector):
+class PyCollector(PyobjMixin, nodes.Collector, abc.ABC):
     def funcnamefilter(self, name: str) -> bool:
         return self._matches_prefix_or_glob_option("python_functions", name)
 
@@ -374,15 +357,19 @@ def classnamefilter(self, name: str) -> bool:
 
     def istestfunction(self, obj: object, name: str) -> bool:
         if self.funcnamefilter(name) or self.isnosetest(obj):
-            if isinstance(obj, staticmethod):
-                # staticmethods need to be unwrapped.
+            if isinstance(obj, (staticmethod, classmethod)):
+                # staticmethods and classmethods need to be unwrapped.
                 obj = safe_getattr(obj, "__func__", False)
             return callable(obj) and fixtures.getfixturemarker(obj) is None
         else:
             return False
 
     def istestclass(self, obj: object, name: str) -> bool:
-        return self.classnamefilter(name) or self.isnosetest(obj)
+        if not (self.classnamefilter(name) or self.isnosetest(obj)):
+            return False
+        if inspect.isabstract(obj):
+            return False
+        return True
 
     def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool:
         """Check if the given name matches the prefix or glob-pattern defined
@@ -399,7 +386,7 @@ def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool:
                 return True
         return False
 
-    def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
+    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
         if not getattr(self.obj, "__test__", True):
             return []
 
@@ -409,13 +396,14 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
             for basecls in self.obj.__mro__:
                 dicts.append(basecls.__dict__)
 
-        # In each class, nodes should be definition ordered. Since Python 3.6,
+        # In each class, nodes should be definition ordered.
         # __dict__ is definition ordered.
-        seen: Set[str] = set()
-        dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = []
+        seen: set[str] = set()
+        dict_values: list[list[nodes.Item | nodes.Collector]] = []
+        collect_imported_tests = self.session.config.getini("collect_imported_tests")
         ihook = self.ihook
         for dic in dicts:
-            values: List[Union[nodes.Item, nodes.Collector]] = []
+            values: list[nodes.Item | nodes.Collector] = []
             # Note: seems like the dict can change during iteration -
             # be careful not to remove the list() without consideration.
             for name, obj in list(dic.items()):
@@ -424,6 +412,13 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
                 if name in seen:
                     continue
                 seen.add(name)
+
+                if not collect_imported_tests and isinstance(self, Module):
+                    # Do not collect functions and classes from other modules.
+                    if inspect.isfunction(obj) or inspect.isclass(obj):
+                        if obj.__module__ != self._getobj().__name__:
+                            continue
+
                 res = ihook.pytest_pycollect_makeitem(
                     collector=self, name=name, obj=obj
                 )
@@ -442,12 +437,12 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
             result.extend(values)
         return result
 
-    def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
+    def _genfunctions(self, name: str, funcobj) -> Iterator[Function]:
         modulecol = self.getparent(Module)
         assert modulecol is not None
         module = modulecol.obj
         clscol = self.getparent(Class)
-        cls = clscol and clscol.obj or None
+        cls = (clscol and clscol.obj) or None
 
         definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
         fixtureinfo = definition._fixtureinfo
@@ -472,17 +467,16 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
         if not metafunc._calls:
             yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
         else:
-            # Add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs.
-            fm = self.session._fixturemanager
-            fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
-
-            # Add_funcarg_pseudo_fixture_def may have shadowed some fixtures
-            # with direct parametrization, so make sure we update what the
-            # function really needs.
+            metafunc._recompute_direct_params_indices()
+            # Direct parametrizations taking place in module/class-specific
+            # `metafunc.parametrize` calls may have shadowed some fixtures, so make sure
+            # we update what the function really needs a.k.a its fixture closure. Note that
+            # direct parametrizations using `@pytest.mark.parametrize` have already been considered
+            # into making the closure using `ignore_args` arg to `getfixtureclosure`.
             fixtureinfo.prune_dependency_tree()
 
             for callspec in metafunc._calls:
-                subname = f"{name}[{callspec.id}]"
+                subname = f"{name}[{callspec.id}]" if callspec._idlist else name
                 yield Function.from_parent(
                     self,
                     name=subname,
@@ -493,57 +487,110 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
                 )
 
 
+def importtestmodule(
+    path: Path,
+    config: Config,
+):
+    # We assume we are only called once per module.
+    importmode = config.getoption("--import-mode")
+    try:
+        mod = import_path(
+            path,
+            mode=importmode,
+            root=config.rootpath,
+            consider_namespace_packages=config.getini("consider_namespace_packages"),
+        )
+    except SyntaxError as e:
+        raise nodes.Collector.CollectError(
+            ExceptionInfo.from_current().getrepr(style="short")
+        ) from e
+    except ImportPathMismatchError as e:
+        raise nodes.Collector.CollectError(
+            "import file mismatch:\n"
+            "imported module {!r} has this __file__ attribute:\n"
+            "  {}\n"
+            "which is not the same as the test file we want to collect:\n"
+            "  {}\n"
+            "HINT: remove __pycache__ / .pyc files and/or use a "
+            "unique basename for your test file modules".format(*e.args)
+        ) from e
+    except ImportError as e:
+        exc_info = ExceptionInfo.from_current()
+        if config.get_verbosity() < 2:
+            exc_info.traceback = exc_info.traceback.filter(filter_traceback)
+        exc_repr = (
+            exc_info.getrepr(style="short")
+            if exc_info.traceback
+            else exc_info.exconly()
+        )
+        formatted_tb = str(exc_repr)
+        raise nodes.Collector.CollectError(
+            f"ImportError while importing test module '{path}'.\n"
+            "Hint: make sure your test modules/packages have valid Python names.\n"
+            "Traceback:\n"
+            f"{formatted_tb}"
+        ) from e
+    except skip.Exception as e:
+        if e.allow_module_level:
+            raise
+        raise nodes.Collector.CollectError(
+            "Using pytest.skip outside of a test will skip the entire module. "
+            "If that's your intention, pass `allow_module_level=True`. "
+            "If you want to skip a specific test or an entire class, "
+            "use the @pytest.mark.skip or @pytest.mark.skipif decorators."
+        ) from e
+    config.pluginmanager.consider_module(mod)
+    return mod
+
+
 class Module(nodes.File, PyCollector):
-    """Collector for test classes and functions."""
+    """Collector for test classes and functions in a Python module."""
 
     def _getobj(self):
-        return self._importtestmodule()
+        return importtestmodule(self.path, self.config)
 
-    def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
-        self._inject_setup_module_fixture()
-        self._inject_setup_function_fixture()
+    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
+        self._register_setup_module_fixture()
+        self._register_setup_function_fixture()
         self.session._fixturemanager.parsefactories(self)
         return super().collect()
 
-    def _inject_setup_module_fixture(self) -> None:
-        """Inject a hidden autouse, module scoped fixture into the collected module object
+    def _register_setup_module_fixture(self) -> None:
+        """Register an autouse, module-scoped fixture for the collected module object
         that invokes setUpModule/tearDownModule if either or both are available.
 
         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
         other fixtures (#517).
         """
-        has_nose = self.config.pluginmanager.has_plugin("nose")
         setup_module = _get_first_non_fixture_func(
             self.obj, ("setUpModule", "setup_module")
         )
-        if setup_module is None and has_nose:
-            setup_module = _get_first_non_fixture_func(self.obj, ("setup",))
         teardown_module = _get_first_non_fixture_func(
             self.obj, ("tearDownModule", "teardown_module")
         )
-        if teardown_module is None and has_nose:
-            teardown_module = _get_first_non_fixture_func(self.obj, ("teardown",))
 
         if setup_module is None and teardown_module is None:
             return
 
-        @fixtures.fixture(
-            autouse=True,
-            scope="module",
-            # Use a unique name to speed up lookup.
-            name=f"_xunit_setup_module_fixture_{self.obj.__name__}",
-        )
-        def xunit_setup_module_fixture(request) -> Generator[None, None, None]:
+        def xunit_setup_module_fixture(request) -> Generator[None]:
+            module = request.module
             if setup_module is not None:
-                _call_with_optional_argument(setup_module, request.module)
+                _call_with_optional_argument(setup_module, module)
             yield
             if teardown_module is not None:
-                _call_with_optional_argument(teardown_module, request.module)
+                _call_with_optional_argument(teardown_module, module)
 
-        self.obj.__pytest_setup_module = xunit_setup_module_fixture
+        self.session._fixturemanager._register_fixture(
+            # Use a unique name to speed up lookup.
+            name=f"_xunit_setup_module_fixture_{self.obj.__name__}",
+            func=xunit_setup_module_fixture,
+            nodeid=self.nodeid,
+            scope="module",
+            autouse=True,
+        )
 
-    def _inject_setup_function_fixture(self) -> None:
-        """Inject a hidden autouse, function scoped fixture into the collected module object
+    def _register_setup_function_fixture(self) -> None:
+        """Register an autouse, function-scoped fixture for the collected module object
         that invokes setup_function/teardown_function if either or both are available.
 
         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
@@ -556,90 +603,58 @@ def _inject_setup_function_fixture(self) -> None:
         if setup_function is None and teardown_function is None:
             return
 
-        @fixtures.fixture(
-            autouse=True,
-            scope="function",
-            # Use a unique name to speed up lookup.
-            name=f"_xunit_setup_function_fixture_{self.obj.__name__}",
-        )
-        def xunit_setup_function_fixture(request) -> Generator[None, None, None]:
+        def xunit_setup_function_fixture(request) -> Generator[None]:
             if request.instance is not None:
                 # in this case we are bound to an instance, so we need to let
                 # setup_method handle this
                 yield
                 return
+            function = request.function
             if setup_function is not None:
-                _call_with_optional_argument(setup_function, request.function)
+                _call_with_optional_argument(setup_function, function)
             yield
             if teardown_function is not None:
-                _call_with_optional_argument(teardown_function, request.function)
+                _call_with_optional_argument(teardown_function, function)
 
-        self.obj.__pytest_setup_function = xunit_setup_function_fixture
+        self.session._fixturemanager._register_fixture(
+            # Use a unique name to speed up lookup.
+            name=f"_xunit_setup_function_fixture_{self.obj.__name__}",
+            func=xunit_setup_function_fixture,
+            nodeid=self.nodeid,
+            scope="function",
+            autouse=True,
+        )
+
+
+class Package(nodes.Directory):
+    """Collector for files and directories in a Python packages -- directories
+    with an `__init__.py` file.
+
+    .. note::
+
+        Directories without an `__init__.py` file are instead collected by
+        :class:`~pytest.Dir` by default. Both are :class:`~pytest.Directory`
+        collectors.
+
+    .. versionchanged:: 8.0
+
+        Now inherits from :class:`~pytest.Directory`.
+    """
 
-    def _importtestmodule(self):
-        # We assume we are only called once per module.
-        importmode = self.config.getoption("--import-mode")
-        try:
-            mod = import_path(self.path, mode=importmode, root=self.config.rootpath)
-        except SyntaxError as e:
-            raise self.CollectError(
-                ExceptionInfo.from_current().getrepr(style="short")
-            ) from e
-        except ImportPathMismatchError as e:
-            raise self.CollectError(
-                "import file mismatch:\n"
-                "imported module %r has this __file__ attribute:\n"
-                "  %s\n"
-                "which is not the same as the test file we want to collect:\n"
-                "  %s\n"
-                "HINT: remove __pycache__ / .pyc files and/or use a "
-                "unique basename for your test file modules" % e.args
-            ) from e
-        except ImportError as e:
-            exc_info = ExceptionInfo.from_current()
-            if self.config.getoption("verbose") < 2:
-                exc_info.traceback = exc_info.traceback.filter(filter_traceback)
-            exc_repr = (
-                exc_info.getrepr(style="short")
-                if exc_info.traceback
-                else exc_info.exconly()
-            )
-            formatted_tb = str(exc_repr)
-            raise self.CollectError(
-                "ImportError while importing test module '{path}'.\n"
-                "Hint: make sure your test modules/packages have valid Python names.\n"
-                "Traceback:\n"
-                "{traceback}".format(path=self.path, traceback=formatted_tb)
-            ) from e
-        except skip.Exception as e:
-            if e.allow_module_level:
-                raise
-            raise self.CollectError(
-                "Using pytest.skip outside of a test will skip the entire module. "
-                "If that's your intention, pass `allow_module_level=True`. "
-                "If you want to skip a specific test or an entire class, "
-                "use the @pytest.mark.skip or @pytest.mark.skipif decorators."
-            ) from e
-        self.config.pluginmanager.consider_module(mod)
-        return mod
-
-
-class Package(Module):
     def __init__(
         self,
-        fspath: Optional[LEGACY_PATH],
+        fspath: LEGACY_PATH | None,
         parent: nodes.Collector,
         # NOTE: following args are unused:
         config=None,
         session=None,
         nodeid=None,
-        path=Optional[Path],
+        path: Path | None = None,
     ) -> None:
         # NOTE: Could be just the following, but kept as-is for compat.
-        # nodes.FSCollector.__init__(self, fspath, parent=parent)
+        # super().__init__(self, fspath, parent=parent)
         session = parent.session
-        nodes.FSCollector.__init__(
-            self,
+        super().__init__(
             fspath=fspath,
             path=path,
             parent=parent,
@@ -647,98 +662,51 @@ def __init__(
             session=session,
             nodeid=nodeid,
         )
-        self.name = self.path.parent.name
 
     def setup(self) -> None:
+        init_mod = importtestmodule(self.path / "__init__.py", self.config)
+
         # Not using fixtures to call setup_module here because autouse fixtures
         # from packages are not called automatically (#4085).
         setup_module = _get_first_non_fixture_func(
-            self.obj, ("setUpModule", "setup_module")
+            init_mod, ("setUpModule", "setup_module")
         )
         if setup_module is not None:
-            _call_with_optional_argument(setup_module, self.obj)
+            _call_with_optional_argument(setup_module, init_mod)
 
         teardown_module = _get_first_non_fixture_func(
-            self.obj, ("tearDownModule", "teardown_module")
+            init_mod, ("tearDownModule", "teardown_module")
         )
         if teardown_module is not None:
-            func = partial(_call_with_optional_argument, teardown_module, self.obj)
+            func = partial(_call_with_optional_argument, teardown_module, init_mod)
             self.addfinalizer(func)
 
-    def gethookproxy(self, fspath: "os.PathLike[str]"):
-        warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
-        return self.session.gethookproxy(fspath)
-
-    def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool:
-        warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
-        return self.session.isinitpath(path)
-
-    def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
-        if direntry.name == "__pycache__":
-            return False
-        fspath = Path(direntry.path)
-        ihook = self.session.gethookproxy(fspath.parent)
-        if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
-            return False
-        norecursepatterns = self.config.getini("norecursedirs")
-        if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns):
-            return False
-        return True
-
-    def _collectfile(
-        self, fspath: Path, handle_dupes: bool = True
-    ) -> Sequence[nodes.Collector]:
-        assert (
-            fspath.is_file()
-        ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
-            fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink()
-        )
-        ihook = self.session.gethookproxy(fspath)
-        if not self.session.isinitpath(fspath):
-            if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
-                return ()
-
-        if handle_dupes:
-            keepduplicates = self.config.getoption("keepduplicates")
-            if not keepduplicates:
-                duplicate_paths = self.config.pluginmanager._duplicatepaths
-                if fspath in duplicate_paths:
-                    return ()
-                else:
-                    duplicate_paths.add(fspath)
-
-        return ihook.pytest_collect_file(file_path=fspath, parent=self)  # type: ignore[no-any-return]
-
-    def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
-        this_path = self.path.parent
-        init_module = this_path / "__init__.py"
-        if init_module.is_file() and path_matches_patterns(
-            init_module, self.config.getini("python_files")
-        ):
-            yield Module.from_parent(self, path=init_module)
-        pkg_prefixes: Set[Path] = set()
-        for direntry in visit(str(this_path), recurse=self._recurse):
-            path = Path(direntry.path)
-
-            # We will visit our own __init__.py file, in which case we skip it.
-            if direntry.is_file():
-                if direntry.name == "__init__.py" and path.parent == this_path:
-                    continue
-
-            parts_ = parts(direntry.path)
-            if any(
-                str(pkg_prefix) in parts_ and pkg_prefix / "__init__.py" != path
-                for pkg_prefix in pkg_prefixes
-            ):
-                continue
+    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
+        # Always collect __init__.py first.
+        def sort_key(entry: os.DirEntry[str]) -> object:
+            return (entry.name != "__init__.py", entry.name)
 
-            if direntry.is_file():
-                yield from self._collectfile(path)
-            elif not direntry.is_dir():
-                # Broken symlink or invalid/missing file.
-                continue
-            elif path.joinpath("__init__.py").is_file():
-                pkg_prefixes.add(path)
+        config = self.config
+        col: nodes.Collector | None
+        cols: Sequence[nodes.Collector]
+        ihook = self.ihook
+        for direntry in scandir(self.path, sort_key):
+            if direntry.is_dir():
+                path = Path(direntry.path)
+                if not self.session.isinitpath(path, with_parents=True):
+                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
+                        continue
+                col = ihook.pytest_collect_directory(path=path, parent=self)
+                if col is not None:
+                    yield col
+
+            elif direntry.is_file():
+                path = Path(direntry.path)
+                if not self.session.isinitpath(path):
+                    if ihook.pytest_ignore_collect(collection_path=path, config=config):
+                        continue
+                cols = ihook.pytest_collect_file(file_path=path, parent=self)
+                yield from cols
 
 
 def _call_with_optional_argument(func, arg) -> None:
@@ -753,37 +721,37 @@ def _call_with_optional_argument(func, arg) -> None:
         func()
 
 
-def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]:
+def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> object | None:
     """Return the attribute from the given object to be used as a setup/teardown
-    xunit-style function, but only if not marked as a fixture to avoid calling it twice."""
+    xunit-style function, but only if not marked as a fixture to avoid calling it twice.
+    """
     for name in names:
-        meth: Optional[object] = getattr(obj, name, None)
+        meth: object | None = getattr(obj, name, None)
         if meth is not None and fixtures.getfixturemarker(meth) is None:
             return meth
     return None
 
 
 class Class(PyCollector):
-    """Collector for test methods."""
+    """Collector for test methods (and nested classes) in a Python class."""
 
     @classmethod
-    def from_parent(cls, parent, *, name, obj=None, **kw):
+    def from_parent(cls, parent, *, name, obj=None, **kw) -> Self:  # type: ignore[override]
         """The public constructor."""
         return super().from_parent(name=name, parent=parent, **kw)
 
     def newinstance(self):
         return self.obj()
 
-    def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
+    def collect(self) -> Iterable[nodes.Item | nodes.Collector]:
         if not safe_getattr(self.obj, "__test__", True):
             return []
         if hasinit(self.obj):
             assert self.parent is not None
             self.warn(
                 PytestCollectionWarning(
-                    "cannot collect test class %r because it has a "
-                    "__init__ constructor (from: %s)"
-                    % (self.obj.__name__, self.parent.nodeid)
+                    f"cannot collect test class {self.obj.__name__!r} because it has a "
+                    f"__init__ constructor (from: {self.parent.nodeid})"
                 )
             )
             return []
@@ -791,105 +759,83 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
             assert self.parent is not None
             self.warn(
                 PytestCollectionWarning(
-                    "cannot collect test class %r because it has a "
-                    "__new__ constructor (from: %s)"
-                    % (self.obj.__name__, self.parent.nodeid)
+                    f"cannot collect test class {self.obj.__name__!r} because it has a "
+                    f"__new__ constructor (from: {self.parent.nodeid})"
                 )
             )
             return []
 
-        self._inject_setup_class_fixture()
-        self._inject_setup_method_fixture()
+        self._register_setup_class_fixture()
+        self._register_setup_method_fixture()
 
         self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid)
 
         return super().collect()
 
-    def _inject_setup_class_fixture(self) -> None:
-        """Inject a hidden autouse, class scoped fixture into the collected class object
+    def _register_setup_class_fixture(self) -> None:
+        """Register an autouse, class scoped fixture into the collected class object
         that invokes setup_class/teardown_class if either or both are available.
 
         Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
         other fixtures (#517).
         """
         setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",))
-        teardown_class = getattr(self.obj, "teardown_class", None)
+        teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",))
         if setup_class is None and teardown_class is None:
             return
 
-        @fixtures.fixture(
-            autouse=True,
-            scope="class",
-            # Use a unique name to speed up lookup.
-            name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}",
-        )
-        def xunit_setup_class_fixture(cls) -> Generator[None, None, None]:
+        def xunit_setup_class_fixture(request) -> Generator[None]:
+            cls = request.cls
             if setup_class is not None:
                 func = getimfunc(setup_class)
-                _call_with_optional_argument(func, self.obj)
+                _call_with_optional_argument(func, cls)
             yield
             if teardown_class is not None:
                 func = getimfunc(teardown_class)
-                _call_with_optional_argument(func, self.obj)
+                _call_with_optional_argument(func, cls)
 
-        self.obj.__pytest_setup_class = xunit_setup_class_fixture
+        self.session._fixturemanager._register_fixture(
+            # Use a unique name to speed up lookup.
+            name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}",
+            func=xunit_setup_class_fixture,
+            nodeid=self.nodeid,
+            scope="class",
+            autouse=True,
+        )
 
-    def _inject_setup_method_fixture(self) -> None:
-        """Inject a hidden autouse, function scoped fixture into the collected class object
+    def _register_setup_method_fixture(self) -> None:
+        """Register an autouse, function scoped fixture into the collected class object
         that invokes setup_method/teardown_method if either or both are available.
 
-        Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
+        Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with
         other fixtures (#517).
         """
-        has_nose = self.config.pluginmanager.has_plugin("nose")
         setup_name = "setup_method"
         setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
-        if setup_method is None and has_nose:
-            setup_name = "setup"
-            setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
         teardown_name = "teardown_method"
-        teardown_method = getattr(self.obj, teardown_name, None)
-        if teardown_method is None and has_nose:
-            teardown_name = "teardown"
-            teardown_method = getattr(self.obj, teardown_name, None)
+        teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,))
         if setup_method is None and teardown_method is None:
             return
 
-        @fixtures.fixture(
-            autouse=True,
-            scope="function",
-            # Use a unique name to speed up lookup.
-            name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}",
-        )
-        def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]:
+        def xunit_setup_method_fixture(request) -> Generator[None]:
+            instance = request.instance
             method = request.function
             if setup_method is not None:
-                func = getattr(self, setup_name)
+                func = getattr(instance, setup_name)
                 _call_with_optional_argument(func, method)
             yield
             if teardown_method is not None:
-                func = getattr(self, teardown_name)
+                func = getattr(instance, teardown_name)
                 _call_with_optional_argument(func, method)
 
-        self.obj.__pytest_setup_method = xunit_setup_method_fixture
-
-
-class InstanceDummy:
-    """Instance used to be a node type between Class and Function. It has been
-    removed in pytest 7.0. Some plugins exist which reference `pytest.Instance`
-    only to ignore it; this dummy class keeps them working. This will be removed
-    in pytest 8."""
-
-    pass
-
-
-# Note: module __getattr__ only works on Python>=3.7. Unfortunately
-# we can't provide this deprecation warning on Python 3.6.
-def __getattr__(name: str) -> object:
-    if name == "Instance":
-        warnings.warn(INSTANCE_COLLECTOR, 2)
-        return InstanceDummy
-    raise AttributeError(f"module {__name__} has no attribute {name}")
+        self.session._fixturemanager._register_fixture(
+            # Use a unique name to speed up lookup.
+            name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}",
+            func=xunit_setup_method_fixture,
+            nodeid=self.nodeid,
+            scope="function",
+            autouse=True,
+        )
 
 
 def hasinit(obj: object) -> bool:
@@ -907,7 +853,196 @@ def hasnew(obj: object) -> bool:
 
 
 @final
-@attr.s(frozen=True, slots=True, auto_attribs=True)
+@dataclasses.dataclass(frozen=True)
+class IdMaker:
+    """Make IDs for a parametrization."""
+
+    __slots__ = (
+        "argnames",
+        "config",
+        "func_name",
+        "idfn",
+        "ids",
+        "nodeid",
+        "parametersets",
+    )
+
+    # The argnames of the parametrization.
+    argnames: Sequence[str]
+    # The ParameterSets of the parametrization.
+    parametersets: Sequence[ParameterSet]
+    # Optionally, a user-provided callable to make IDs for parameters in a
+    # ParameterSet.
+    idfn: Callable[[Any], object | None] | None
+    # Optionally, explicit IDs for ParameterSets by index.
+    ids: Sequence[object | None] | None
+    # Optionally, the pytest config.
+    # Used for controlling ASCII escaping, and for calling the
+    # :hook:`pytest_make_parametrize_id` hook.
+    config: Config | None
+    # Optionally, the ID of the node being parametrized.
+    # Used only for clearer error messages.
+    nodeid: str | None
+    # Optionally, the ID of the function being parametrized.
+    # Used only for clearer error messages.
+    func_name: str | None
+
+    def make_unique_parameterset_ids(self) -> list[str | _HiddenParam]:
+        """Make a unique identifier for each ParameterSet, that may be used to
+        identify the parametrization in a node ID.
+
+        Format is <prm_1_token>-...-<prm_n_token>[counter], where prm_x_token is
+        - user-provided id, if given
+        - else an id derived from the value, applicable for certain types
+        - else <argname><parameterset index>
+        The counter suffix is appended only in case a string wouldn't be unique
+        otherwise.
+        """
+        resolved_ids = list(self._resolve_ids())
+        # All IDs must be unique!
+        if len(resolved_ids) != len(set(resolved_ids)):
+            # Record the number of occurrences of each ID.
+            id_counts = Counter(resolved_ids)
+            # Map the ID to its next suffix.
+            id_suffixes: dict[str, int] = defaultdict(int)
+            # Suffix non-unique IDs to make them unique.
+            for index, id in enumerate(resolved_ids):
+                if id_counts[id] > 1:
+                    if id is HIDDEN_PARAM:
+                        self._complain_multiple_hidden_parameter_sets()
+                    suffix = ""
+                    if id and id[-1].isdigit():
+                        suffix = "_"
+                    new_id = f"{id}{suffix}{id_suffixes[id]}"
+                    while new_id in set(resolved_ids):
+                        id_suffixes[id] += 1
+                        new_id = f"{id}{suffix}{id_suffixes[id]}"
+                    resolved_ids[index] = new_id
+                    id_suffixes[id] += 1
+        assert len(resolved_ids) == len(set(resolved_ids)), (
+            f"Internal error: {resolved_ids=}"
+        )
+        return resolved_ids
+
+    def _resolve_ids(self) -> Iterable[str | _HiddenParam]:
+        """Resolve IDs for all ParameterSets (may contain duplicates)."""
+        for idx, parameterset in enumerate(self.parametersets):
+            if parameterset.id is not None:
+                # ID provided directly - pytest.param(..., id="...")
+                if parameterset.id is HIDDEN_PARAM:
+                    yield HIDDEN_PARAM
+                else:
+                    yield _ascii_escaped_by_config(parameterset.id, self.config)
+            elif self.ids and idx < len(self.ids) and self.ids[idx] is not None:
+                # ID provided in the IDs list - parametrize(..., ids=[...]).
+                if self.ids[idx] is HIDDEN_PARAM:
+                    yield HIDDEN_PARAM
+                else:
+                    yield self._idval_from_value_required(self.ids[idx], idx)
+            else:
+                # ID not provided - generate it.
+                yield "-".join(
+                    self._idval(val, argname, idx)
+                    for val, argname in zip(parameterset.values, self.argnames)
+                )
+
+    def _idval(self, val: object, argname: str, idx: int) -> str:
+        """Make an ID for a parameter in a ParameterSet."""
+        idval = self._idval_from_function(val, argname, idx)
+        if idval is not None:
+            return idval
+        idval = self._idval_from_hook(val, argname)
+        if idval is not None:
+            return idval
+        idval = self._idval_from_value(val)
+        if idval is not None:
+            return idval
+        return self._idval_from_argname(argname, idx)
+
+    def _idval_from_function(self, val: object, argname: str, idx: int) -> str | None:
+        """Try to make an ID for a parameter in a ParameterSet using the
+        user-provided id callable, if given."""
+        if self.idfn is None:
+            return None
+        try:
+            id = self.idfn(val)
+        except Exception as e:
+            prefix = f"{self.nodeid}: " if self.nodeid is not None else ""
+            msg = "error raised while trying to determine id of parameter '{}' at position {}"
+            msg = prefix + msg.format(argname, idx)
+            raise ValueError(msg) from e
+        if id is None:
+            return None
+        return self._idval_from_value(id)
+
+    def _idval_from_hook(self, val: object, argname: str) -> str | None:
+        """Try to make an ID for a parameter in a ParameterSet by calling the
+        :hook:`pytest_make_parametrize_id` hook."""
+        if self.config:
+            id: str | None = self.config.hook.pytest_make_parametrize_id(
+                config=self.config, val=val, argname=argname
+            )
+            return id
+        return None
+
+    def _idval_from_value(self, val: object) -> str | None:
+        """Try to make an ID for a parameter in a ParameterSet from its value,
+        if the value type is supported."""
+        if isinstance(val, (str, bytes)):
+            return _ascii_escaped_by_config(val, self.config)
+        elif val is None or isinstance(val, (float, int, bool, complex)):
+            return str(val)
+        elif isinstance(val, re.Pattern):
+            return ascii_escaped(val.pattern)
+        elif val is NOTSET:
+            # Fallback to default. Note that NOTSET is an enum.Enum.
+            pass
+        elif isinstance(val, enum.Enum):
+            return str(val)
+        elif isinstance(getattr(val, "__name__", None), str):
+            # Name of a class, function, module, etc.
+            name: str = getattr(val, "__name__")
+            return name
+        return None
+
+    def _idval_from_value_required(self, val: object, idx: int) -> str:
+        """Like _idval_from_value(), but fails if the type is not supported."""
+        id = self._idval_from_value(val)
+        if id is not None:
+            return id
+
+        # Fail.
+        prefix = self._make_error_prefix()
+        msg = (
+            f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. "
+            "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
+        )
+        fail(msg, pytrace=False)
+
+    @staticmethod
+    def _idval_from_argname(argname: str, idx: int) -> str:
+        """Make an ID for a parameter in a ParameterSet from the argument name
+        and the index of the ParameterSet."""
+        return str(argname) + str(idx)
+
+    def _complain_multiple_hidden_parameter_sets(self) -> NoReturn:
+        fail(
+            f"{self._make_error_prefix()}multiple instances of HIDDEN_PARAM "
+            "cannot be used in the same parametrize call, "
+            "because the tests names need to be unique."
+        )
+
+    def _make_error_prefix(self) -> str:
+        if self.func_name is not None:
+            return f"In {self.func_name}: "
+        elif self.nodeid is not None:
+            return f"In {self.nodeid}: "
+        else:
+            return ""
+
+
+@final
+@dataclasses.dataclass(frozen=True)
 class CallSpec2:
     """A planned parameterized invocation of a test function.
 
@@ -916,54 +1051,42 @@ class CallSpec2:
     and stored in item.callspec.
     """
 
-    # arg name -> arg value which will be passed to the parametrized test
-    # function (direct parameterization).
-    funcargs: Dict[str, object] = attr.Factory(dict)
-    # arg name -> arg value which will be passed to a fixture of the same name
-    # (indirect parametrization).
-    params: Dict[str, object] = attr.Factory(dict)
+    # arg name -> arg value which will be passed to a fixture or pseudo-fixture
+    # of the same name. (indirect or direct parametrization respectively)
+    params: dict[str, object] = dataclasses.field(default_factory=dict)
     # arg name -> arg index.
-    indices: Dict[str, int] = attr.Factory(dict)
+    indices: dict[str, int] = dataclasses.field(default_factory=dict)
     # Used for sorting parametrized resources.
-    _arg2scope: Dict[str, Scope] = attr.Factory(dict)
+    _arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict)
     # Parts which will be added to the item's name in `[..]` separated by "-".
-    _idlist: List[str] = attr.Factory(list)
+    _idlist: Sequence[str] = dataclasses.field(default_factory=tuple)
     # Marks which will be applied to the item.
-    marks: List[Mark] = attr.Factory(list)
+    marks: list[Mark] = dataclasses.field(default_factory=list)
 
     def setmulti(
         self,
         *,
-        valtypes: Mapping[str, "Literal['params', 'funcargs']"],
         argnames: Iterable[str],
         valset: Iterable[object],
-        id: str,
-        marks: Iterable[Union[Mark, MarkDecorator]],
+        id: str | _HiddenParam,
+        marks: Iterable[Mark | MarkDecorator],
         scope: Scope,
         param_index: int,
-    ) -> "CallSpec2":
-        funcargs = self.funcargs.copy()
+    ) -> CallSpec2:
         params = self.params.copy()
         indices = self.indices.copy()
-        arg2scope = self._arg2scope.copy()
+        arg2scope = dict(self._arg2scope)
         for arg, val in zip(argnames, valset):
-            if arg in params or arg in funcargs:
-                raise ValueError(f"duplicate {arg!r}")
-            valtype_for_arg = valtypes[arg]
-            if valtype_for_arg == "params":
-                params[arg] = val
-            elif valtype_for_arg == "funcargs":
-                funcargs[arg] = val
-            else:
-                assert_never(valtype_for_arg)
+            if arg in params:
+                raise ValueError(f"duplicate parametrization of {arg!r}")
+            params[arg] = val
             indices[arg] = param_index
             arg2scope[arg] = scope
         return CallSpec2(
-            funcargs=funcargs,
             params=params,
-            arg2scope=arg2scope,
             indices=indices,
-            idlist=[*self._idlist, id],
+            _arg2scope=arg2scope,
+            _idlist=self._idlist if id is HIDDEN_PARAM else [*self._idlist, id],
             marks=[*self.marks, *normalize_mark_list(marks)],
         )
 
@@ -978,9 +1101,17 @@ def id(self) -> str:
         return "-".join(self._idlist)
 
 
+def get_direct_param_fixture_func(request: FixtureRequest) -> Any:
+    return request.param
+
+
+# Used for storing pseudo fixturedefs for direct parametrization.
+name2pseudofixturedef_key = StashKey[dict[str, FixtureDef[Any]]]()
+
+
 @final
 class Metafunc:
-    """Objects passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
+    """Objects passed to the :hook:`pytest_generate_tests` hook.
 
     They help to inspect a test function and to generate tests according to
     test configuration or values specified in the class or module where a
@@ -989,7 +1120,7 @@ class Metafunc:
 
     def __init__(
         self,
-        definition: "FunctionDefinition",
+        definition: FunctionDefinition,
         fixtureinfo: fixtures.FuncFixtureInfo,
         config: Config,
         cls=None,
@@ -1020,30 +1151,28 @@ def __init__(
         self._arg2fixturedefs = fixtureinfo.name2fixturedefs
 
         # Result of parametrize().
-        self._calls: List[CallSpec2] = []
+        self._calls: list[CallSpec2] = []
+
+        self._params_directness: dict[str, Literal["indirect", "direct"]] = {}
 
     def parametrize(
         self,
-        argnames: Union[str, List[str], Tuple[str, ...]],
-        argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
-        indirect: Union[bool, Sequence[str]] = False,
-        ids: Optional[
-            Union[
-                Iterable[Union[None, str, float, int, bool]],
-                Callable[[Any], Optional[object]],
-            ]
-        ] = None,
-        scope: "Optional[_ScopeName]" = None,
+        argnames: str | Sequence[str],
+        argvalues: Iterable[ParameterSet | Sequence[object] | object],
+        indirect: bool | Sequence[str] = False,
+        ids: Iterable[object | None] | Callable[[Any], object | None] | None = None,
+        scope: _ScopeName | None = None,
         *,
-        _param_mark: Optional[Mark] = None,
+        _param_mark: Mark | None = None,
     ) -> None:
         """Add new invocations to the underlying test function using the list
         of argvalues for the given argnames. Parametrization is performed
         during the collection phase. If you need to setup expensive resources
         see about setting indirect to do it rather than at test setup time.
 
-        Can be called multiple times, in which case each call parametrizes all
-        previous parametrizations, e.g.
+        Can be called multiple times per test function (but only on different
+        argument names), in which case each call parametrizes all previous
+        parametrizations, e.g.
 
         ::
 
@@ -1063,7 +1192,7 @@ def parametrize(
             If N argnames were specified, argvalues must be a list of
             N-tuples, where each tuple-element specifies a value for its
             respective argname.
-
+        :type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object]
         :param indirect:
             A list of arguments' names (subset of argnames) or a boolean.
             If True the list contains all names from the argnames. Each
@@ -1082,6 +1211,11 @@ def parametrize(
             They are mapped to the corresponding index in ``argvalues``.
             ``None`` means to use the auto-generated id.
 
+            .. versionadded:: 8.4
+                :ref:`hidden-param` means to hide the parameter set
+                from the test name. Can only be used at most 1 time, as
+                test names need to be unique.
+
             If it is a callable it will be called for each entry in
             ``argvalues``, and the return value is used as part of the
             auto-generated id for the whole set (where parts are joined with
@@ -1098,7 +1232,7 @@ def parametrize(
             It will also override any fixture-function defined scope, allowing
             to set a dynamic scope using test context or configuration.
         """
-        argnames, parameters = ParameterSet._for_parametrize(
+        argnames, parametersets = ParameterSet._for_parametrize(
             argnames,
             argvalues,
             self.function,
@@ -1122,30 +1256,82 @@ def parametrize(
 
         self._validate_if_using_arg_names(argnames, indirect)
 
-        arg_values_types = self._resolve_arg_value_types(argnames, indirect)
-
         # Use any already (possibly) generated ids with parametrize Marks.
         if _param_mark and _param_mark._param_ids_from:
             generated_ids = _param_mark._param_ids_from._param_ids_generated
             if generated_ids is not None:
                 ids = generated_ids
 
-        ids = self._resolve_arg_ids(
-            argnames, ids, parameters, nodeid=self.definition.nodeid
+        ids = self._resolve_parameter_set_ids(
+            argnames, ids, parametersets, nodeid=self.definition.nodeid
         )
 
         # Store used (possibly generated) ids with parametrize Marks.
         if _param_mark and _param_mark._param_ids_from and generated_ids is None:
             object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
 
+        # Add funcargs as fixturedefs to fixtureinfo.arg2fixturedefs by registering
+        # artificial "pseudo" FixtureDef's so that later at test execution time we can
+        # rely on a proper FixtureDef to exist for fixture setup.
+        node = None
+        # If we have a scope that is higher than function, we need
+        # to make sure we only ever create an according fixturedef on
+        # a per-scope basis. We thus store and cache the fixturedef on the
+        # node related to the scope.
+        if scope_ is not Scope.Function:
+            collector = self.definition.parent
+            assert collector is not None
+            node = get_scope_node(collector, scope_)
+            if node is None:
+                # If used class scope and there is no class, use module-level
+                # collector (for now).
+                if scope_ is Scope.Class:
+                    assert isinstance(collector, Module)
+                    node = collector
+                # If used package scope and there is no package, use session
+                # (for now).
+                elif scope_ is Scope.Package:
+                    node = collector.session
+                else:
+                    assert False, f"Unhandled missing scope: {scope}"
+        if node is None:
+            name2pseudofixturedef = None
+        else:
+            default: dict[str, FixtureDef[Any]] = {}
+            name2pseudofixturedef = node.stash.setdefault(
+                name2pseudofixturedef_key, default
+            )
+        arg_directness = self._resolve_args_directness(argnames, indirect)
+        self._params_directness.update(arg_directness)
+        for argname in argnames:
+            if arg_directness[argname] == "indirect":
+                continue
+            if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
+                fixturedef = name2pseudofixturedef[argname]
+            else:
+                fixturedef = FixtureDef(
+                    config=self.config,
+                    baseid="",
+                    argname=argname,
+                    func=get_direct_param_fixture_func,
+                    scope=scope_,
+                    params=None,
+                    ids=None,
+                    _ispytest=True,
+                )
+                if name2pseudofixturedef is not None:
+                    name2pseudofixturedef[argname] = fixturedef
+            self._arg2fixturedefs[argname] = [fixturedef]
+
         # Create the new calls: if we are parametrize() multiple times (by applying the decorator
         # more than once) then we accumulate those calls generating the cartesian product
         # of all calls.
         newcalls = []
         for callspec in self._calls or [CallSpec2()]:
-            for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
+            for param_index, (param_id, param_set) in enumerate(
+                zip(ids, parametersets)
+            ):
                 newcallspec = callspec.setmulti(
-                    valtypes=arg_values_types,
                     argnames=argnames,
                     valset=param_set.values,
                     id=param_id,
@@ -1156,27 +1342,27 @@ def parametrize(
                 newcalls.append(newcallspec)
         self._calls = newcalls
 
-    def _resolve_arg_ids(
+    def _resolve_parameter_set_ids(
         self,
         argnames: Sequence[str],
-        ids: Optional[
-            Union[
-                Iterable[Union[None, str, float, int, bool]],
-                Callable[[Any], Optional[object]],
-            ]
-        ],
-        parameters: Sequence[ParameterSet],
+        ids: Iterable[object | None] | Callable[[Any], object | None] | None,
+        parametersets: Sequence[ParameterSet],
         nodeid: str,
-    ) -> List[str]:
-        """Resolve the actual ids for the given argnames, based on the ``ids`` parameter given
-        to ``parametrize``.
+    ) -> list[str | _HiddenParam]:
+        """Resolve the actual ids for the given parameter sets.
 
-        :param List[str] argnames: List of argument names passed to ``parametrize()``.
-        :param ids: The ids parameter of the parametrized call (see docs).
-        :param List[ParameterSet] parameters: The list of parameter values, same size as ``argnames``.
-        :param str str: The nodeid of the item that generated this parametrized call.
-        :rtype: List[str]
-        :returns: The list of ids for each argname given.
+        :param argnames:
+            Argument names passed to ``parametrize()``.
+        :param ids:
+            The `ids` parameter of the ``parametrize()`` call (see docs).
+        :param parametersets:
+            The parameter sets, each containing a set of values corresponding
+            to ``argnames``.
+        :param nodeid str:
+            The nodeid of the definition item that generated this
+            parametrization.
+        :returns:
+            List with ids for each parameter set given.
         """
         if ids is None:
             idfn = None
@@ -1186,15 +1372,24 @@ def _resolve_arg_ids(
             ids_ = None
         else:
             idfn = None
-            ids_ = self._validate_ids(ids, parameters, self.function.__name__)
-        return idmaker(argnames, parameters, idfn, ids_, self.config, nodeid=nodeid)
+            ids_ = self._validate_ids(ids, parametersets, self.function.__name__)
+        id_maker = IdMaker(
+            argnames,
+            parametersets,
+            idfn,
+            ids_,
+            self.config,
+            nodeid=nodeid,
+            func_name=self.function.__name__,
+        )
+        return id_maker.make_unique_parameterset_ids()
 
     def _validate_ids(
         self,
-        ids: Iterable[Union[None, str, float, int, bool]],
-        parameters: Sequence[ParameterSet],
+        ids: Iterable[object | None],
+        parametersets: Sequence[ParameterSet],
         func_name: str,
-    ) -> List[Union[None, str]]:
+    ) -> list[object | None]:
         try:
             num_ids = len(ids)  # type: ignore[arg-type]
         except TypeError:
@@ -1202,74 +1397,58 @@ def _validate_ids(
                 iter(ids)
             except TypeError as e:
                 raise TypeError("ids must be a callable or an iterable") from e
-            num_ids = len(parameters)
+            num_ids = len(parametersets)
 
         # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849
-        if num_ids != len(parameters) and num_ids != 0:
+        if num_ids != len(parametersets) and num_ids != 0:
             msg = "In {}: {} parameter sets specified, with different number of ids: {}"
-            fail(msg.format(func_name, len(parameters), num_ids), pytrace=False)
-
-        new_ids = []
-        for idx, id_value in enumerate(itertools.islice(ids, num_ids)):
-            if id_value is None or isinstance(id_value, str):
-                new_ids.append(id_value)
-            elif isinstance(id_value, (float, int, bool)):
-                new_ids.append(str(id_value))
-            else:
-                msg = (  # type: ignore[unreachable]
-                    "In {}: ids must be list of string/float/int/bool, "
-                    "found: {} (type: {!r}) at index {}"
-                )
-                fail(
-                    msg.format(func_name, saferepr(id_value), type(id_value), idx),
-                    pytrace=False,
-                )
-        return new_ids
+            fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False)
+
+        return list(itertools.islice(ids, num_ids))
 
-    def _resolve_arg_value_types(
+    def _resolve_args_directness(
         self,
         argnames: Sequence[str],
-        indirect: Union[bool, Sequence[str]],
-    ) -> Dict[str, "Literal['params', 'funcargs']"]:
-        """Resolve if each parametrized argument must be considered a
-        parameter to a fixture or a "funcarg" to the function, based on the
-        ``indirect`` parameter of the parametrized() call.
+        indirect: bool | Sequence[str],
+    ) -> dict[str, Literal["indirect", "direct"]]:
+        """Resolve if each parametrized argument must be considered an indirect
+        parameter to a fixture of the same name, or a direct parameter to the
+        parametrized function, based on the ``indirect`` parameter of the
+        parametrized() call.
 
-        :param List[str] argnames: List of argument names passed to ``parametrize()``.
-        :param indirect: Same as the ``indirect`` parameter of ``parametrize()``.
-        :rtype: Dict[str, str]
-            A dict mapping each arg name to either:
-            * "params" if the argname should be the parameter of a fixture of the same name.
-            * "funcargs" if the argname should be a parameter to the parametrized test function.
+        :param argnames:
+            List of argument names passed to ``parametrize()``.
+        :param indirect:
+            Same as the ``indirect`` parameter of ``parametrize()``.
+        :returns
+            A dict mapping each arg name to either "indirect" or "direct".
         """
+        arg_directness: dict[str, Literal["indirect", "direct"]]
         if isinstance(indirect, bool):
-            valtypes: Dict[str, Literal["params", "funcargs"]] = dict.fromkeys(
-                argnames, "params" if indirect else "funcargs"
+            arg_directness = dict.fromkeys(
+                argnames, "indirect" if indirect else "direct"
             )
         elif isinstance(indirect, Sequence):
-            valtypes = dict.fromkeys(argnames, "funcargs")
+            arg_directness = dict.fromkeys(argnames, "direct")
             for arg in indirect:
                 if arg not in argnames:
                     fail(
-                        "In {}: indirect fixture '{}' doesn't exist".format(
-                            self.function.__name__, arg
-                        ),
+                        f"In {self.function.__name__}: indirect fixture '{arg}' doesn't exist",
                         pytrace=False,
                     )
-                valtypes[arg] = "params"
+                arg_directness[arg] = "indirect"
         else:
             fail(
-                "In {func}: expected Sequence or boolean for indirect, got {type}".format(
-                    type=type(indirect).__name__, func=self.function.__name__
-                ),
+                f"In {self.function.__name__}: expected Sequence or boolean"
+                f" for indirect, got {type(indirect).__name__}",
                 pytrace=False,
             )
-        return valtypes
+        return arg_directness
 
     def _validate_if_using_arg_names(
         self,
         argnames: Sequence[str],
-        indirect: Union[bool, Sequence[str]],
+        indirect: bool | Sequence[str],
     ) -> None:
         """Check if all argnames are being used, by default values, or directly/indirectly.
 
@@ -1283,9 +1462,7 @@ def _validate_if_using_arg_names(
             if arg not in self.fixturenames:
                 if arg in default_arg_names:
                     fail(
-                        "In {}: function already takes an argument '{}' with a default value".format(
-                            func_name, arg
-                        ),
+                        f"In {func_name}: function already takes an argument '{arg}' with a default value",
                         pytrace=False,
                     )
                 else:
@@ -1298,11 +1475,17 @@ def _validate_if_using_arg_names(
                         pytrace=False,
                     )
 
+    def _recompute_direct_params_indices(self) -> None:
+        for argname, param_type in self._params_directness.items():
+            if param_type == "direct":
+                for i, callspec in enumerate(self._calls):
+                    callspec.indices[argname] = i
+
 
 def _find_parametrized_scope(
     argnames: Sequence[str],
     arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]],
-    indirect: Union[bool, Sequence[str]],
+    indirect: bool | Sequence[str],
 ) -> Scope:
     """Find the most appropriate scope for a parametrized call based on its arguments.
 
@@ -1321,7 +1504,7 @@ def _find_parametrized_scope(
     if all_arguments_are_fixtures:
         fixturedefs = arg2fixturedefs or {}
         used_scopes = [
-            fixturedef[0]._scope
+            fixturedef[-1]._scope
             for name, fixturedef in fixturedefs.items()
             if name in argnames
         ]
@@ -1331,7 +1514,7 @@ def _find_parametrized_scope(
     return Scope.Function
 
 
-def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str:
+def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str:
     if config is None:
         escape_option = False
     else:
@@ -1344,239 +1527,8 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -
     return val if escape_option else ascii_escaped(val)  # type: ignore
 
 
-def _idval(
-    val: object,
-    argname: str,
-    idx: int,
-    idfn: Optional[Callable[[Any], Optional[object]]],
-    nodeid: Optional[str],
-    config: Optional[Config],
-) -> str:
-    if idfn:
-        try:
-            generated_id = idfn(val)
-            if generated_id is not None:
-                val = generated_id
-        except Exception as e:
-            prefix = f"{nodeid}: " if nodeid is not None else ""
-            msg = "error raised while trying to determine id of parameter '{}' at position {}"
-            msg = prefix + msg.format(argname, idx)
-            raise ValueError(msg) from e
-    elif config:
-        hook_id: Optional[str] = config.hook.pytest_make_parametrize_id(
-            config=config, val=val, argname=argname
-        )
-        if hook_id:
-            return hook_id
-
-    if isinstance(val, STRING_TYPES):
-        return _ascii_escaped_by_config(val, config)
-    elif val is None or isinstance(val, (float, int, bool, complex)):
-        return str(val)
-    elif isinstance(val, Pattern):
-        return ascii_escaped(val.pattern)
-    elif val is NOTSET:
-        # Fallback to default. Note that NOTSET is an enum.Enum.
-        pass
-    elif isinstance(val, enum.Enum):
-        return str(val)
-    elif isinstance(getattr(val, "__name__", None), str):
-        # Name of a class, function, module, etc.
-        name: str = getattr(val, "__name__")
-        return name
-    return str(argname) + str(idx)
-
-
-def _idvalset(
-    idx: int,
-    parameterset: ParameterSet,
-    argnames: Iterable[str],
-    idfn: Optional[Callable[[Any], Optional[object]]],
-    ids: Optional[List[Union[None, str]]],
-    nodeid: Optional[str],
-    config: Optional[Config],
-) -> str:
-    if parameterset.id is not None:
-        return parameterset.id
-    id = None if ids is None or idx >= len(ids) else ids[idx]
-    if id is None:
-        this_id = [
-            _idval(val, argname, idx, idfn, nodeid=nodeid, config=config)
-            for val, argname in zip(parameterset.values, argnames)
-        ]
-        return "-".join(this_id)
-    else:
-        return _ascii_escaped_by_config(id, config)
-
-
-def idmaker(
-    argnames: Iterable[str],
-    parametersets: Iterable[ParameterSet],
-    idfn: Optional[Callable[[Any], Optional[object]]] = None,
-    ids: Optional[List[Union[None, str]]] = None,
-    config: Optional[Config] = None,
-    nodeid: Optional[str] = None,
-) -> List[str]:
-    resolved_ids = [
-        _idvalset(
-            valindex, parameterset, argnames, idfn, ids, config=config, nodeid=nodeid
-        )
-        for valindex, parameterset in enumerate(parametersets)
-    ]
-
-    # All IDs must be unique!
-    unique_ids = set(resolved_ids)
-    if len(unique_ids) != len(resolved_ids):
-
-        # Record the number of occurrences of each test ID.
-        test_id_counts = Counter(resolved_ids)
-
-        # Map the test ID to its next suffix.
-        test_id_suffixes: Dict[str, int] = defaultdict(int)
-
-        # Suffix non-unique IDs to make them unique.
-        for index, test_id in enumerate(resolved_ids):
-            if test_id_counts[test_id] > 1:
-                resolved_ids[index] = f"{test_id}{test_id_suffixes[test_id]}"
-                test_id_suffixes[test_id] += 1
-
-    return resolved_ids
-
-
-def _pretty_fixture_path(func) -> str:
-    cwd = Path.cwd()
-    loc = Path(getlocation(func, str(cwd)))
-    prefix = Path("...", "_pytest")
-    try:
-        return str(prefix / loc.relative_to(_PYTEST_DIR))
-    except ValueError:
-        return bestrelpath(cwd, loc)
-
-
-def show_fixtures_per_test(config):
-    from _pytest.main import wrap_session
-
-    return wrap_session(config, _show_fixtures_per_test)
-
-
-def _show_fixtures_per_test(config: Config, session: Session) -> None:
-    import _pytest.config
-
-    session.perform_collect()
-    curdir = Path.cwd()
-    tw = _pytest.config.create_terminal_writer(config)
-    verbose = config.getvalue("verbose")
-
-    def get_best_relpath(func) -> str:
-        loc = getlocation(func, str(curdir))
-        return bestrelpath(curdir, Path(loc))
-
-    def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
-        argname = fixture_def.argname
-        if verbose <= 0 and argname.startswith("_"):
-            return
-        prettypath = _pretty_fixture_path(fixture_def.func)
-        tw.write(f"{argname}", green=True)
-        tw.write(f" -- {prettypath}", yellow=True)
-        tw.write("\n")
-        fixture_doc = inspect.getdoc(fixture_def.func)
-        if fixture_doc:
-            write_docstring(
-                tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc
-            )
-        else:
-            tw.line("    no docstring available", red=True)
-
-    def write_item(item: nodes.Item) -> None:
-        # Not all items have _fixtureinfo attribute.
-        info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None)
-        if info is None or not info.name2fixturedefs:
-            # This test item does not use any fixtures.
-            return
-        tw.line()
-        tw.sep("-", f"fixtures used by {item.name}")
-        # TODO: Fix this type ignore.
-        tw.sep("-", f"({get_best_relpath(item.function)})")  # type: ignore[attr-defined]
-        # dict key not used in loop but needed for sorting.
-        for _, fixturedefs in sorted(info.name2fixturedefs.items()):
-            assert fixturedefs is not None
-            if not fixturedefs:
-                continue
-            # Last item is expected to be the one used by the test item.
-            write_fixture(fixturedefs[-1])
-
-    for session_item in session.items:
-        write_item(session_item)
-
-
-def showfixtures(config: Config) -> Union[int, ExitCode]:
-    from _pytest.main import wrap_session
-
-    return wrap_session(config, _showfixtures_main)
-
-
-def _showfixtures_main(config: Config, session: Session) -> None:
-    import _pytest.config
-
-    session.perform_collect()
-    curdir = Path.cwd()
-    tw = _pytest.config.create_terminal_writer(config)
-    verbose = config.getvalue("verbose")
-
-    fm = session._fixturemanager
-
-    available = []
-    seen: Set[Tuple[str, str]] = set()
-
-    for argname, fixturedefs in fm._arg2fixturedefs.items():
-        assert fixturedefs is not None
-        if not fixturedefs:
-            continue
-        for fixturedef in fixturedefs:
-            loc = getlocation(fixturedef.func, str(curdir))
-            if (fixturedef.argname, loc) in seen:
-                continue
-            seen.add((fixturedef.argname, loc))
-            available.append(
-                (
-                    len(fixturedef.baseid),
-                    fixturedef.func.__module__,
-                    _pretty_fixture_path(fixturedef.func),
-                    fixturedef.argname,
-                    fixturedef,
-                )
-            )
-
-    available.sort()
-    currentmodule = None
-    for baseid, module, prettypath, argname, fixturedef in available:
-        if currentmodule != module:
-            if not module.startswith("_pytest."):
-                tw.line()
-                tw.sep("-", f"fixtures defined from {module}")
-                currentmodule = module
-        if verbose <= 0 and argname.startswith("_"):
-            continue
-        tw.write(f"{argname}", green=True)
-        if fixturedef.scope != "function":
-            tw.write(" [%s scope]" % fixturedef.scope, cyan=True)
-        tw.write(f" -- {prettypath}", yellow=True)
-        tw.write("\n")
-        doc = inspect.getdoc(fixturedef.func)
-        if doc:
-            write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc)
-        else:
-            tw.line("    no docstring available", red=True)
-        tw.line()
-
-
-def write_docstring(tw: TerminalWriter, doc: str, indent: str = "    ") -> None:
-    for line in doc.split("\n"):
-        tw.line(indent + line)
-
-
 class Function(PyobjMixin, nodes.Item):
-    """An Item responsible for setting up and executing a Python test function.
+    """Item responsible for setting up and executing a Python test function.
 
     :param name:
         The full function name, including any decorations like those
@@ -1586,7 +1538,7 @@ class Function(PyobjMixin, nodes.Item):
     :param config:
         The pytest Config object.
     :param callspec:
-        If given, this is function has been parametrized and the callspec contains
+        If given, this function has been parametrized and the callspec contains
         meta information about the parametrization.
     :param callobj:
         If given, the object which will be called when the Function is invoked,
@@ -1611,18 +1563,19 @@ def __init__(
         self,
         name: str,
         parent,
-        config: Optional[Config] = None,
-        callspec: Optional[CallSpec2] = None,
+        config: Config | None = None,
+        callspec: CallSpec2 | None = None,
         callobj=NOTSET,
-        keywords=None,
-        session: Optional[Session] = None,
-        fixtureinfo: Optional[FuncFixtureInfo] = None,
-        originalname: Optional[str] = None,
+        keywords: Mapping[str, Any] | None = None,
+        session: Session | None = None,
+        fixtureinfo: FuncFixtureInfo | None = None,
+        originalname: str | None = None,
     ) -> None:
         super().__init__(name, parent, config=config, session=session)
 
         if callobj is not NOTSET:
-            self.obj = callobj
+            self._obj = callobj
+            self._instance = getattr(callobj, "__self__", None)
 
         #: Original function name, without any decorations (for example
         #: parametrization adds a ``"[...]"`` suffix to function names), used to access
@@ -1635,48 +1588,37 @@ def __init__(
         # Note: when FunctionDefinition is introduced, we should change ``originalname``
         # to a readonly property that returns FunctionDefinition.name.
 
-        self.keywords.update(self.obj.__dict__)
         self.own_markers.extend(get_unpacked_marks(self.obj))
         if callspec:
             self.callspec = callspec
-            # this is total hostile and a mess
-            # keywords are broken by design by now
-            # this will be redeemed later
-            for mark in callspec.marks:
-                # feel free to cry, this was broken for years before
-                # and keywords cant fix it per design
-                self.keywords[mark.name] = mark
-            self.own_markers.extend(normalize_mark_list(callspec.marks))
-        if keywords:
-            self.keywords.update(keywords)
+            self.own_markers.extend(callspec.marks)
 
         # todo: this is a hell of a hack
         # https://github.com/pytest-dev/pytest/issues/4569
-
-        self.keywords.update(
-            {
-                mark.name: True
-                for mark in self.iter_markers()
-                if mark.name not in self.keywords
-            }
-        )
+        # Note: the order of the updates is important here; indicates what
+        # takes priority (ctor argument over function attributes over markers).
+        # Take own_markers only; NodeKeywords handles parent traversal on its own.
+        self.keywords.update((mark.name, mark) for mark in self.own_markers)
+        self.keywords.update(self.obj.__dict__)
+        if keywords:
+            self.keywords.update(keywords)
 
         if fixtureinfo is None:
-            fixtureinfo = self.session._fixturemanager.getfixtureinfo(
-                self, self.obj, self.cls, funcargs=True
-            )
+            fm = self.session._fixturemanager
+            fixtureinfo = fm.getfixtureinfo(self, self.obj, self.cls)
         self._fixtureinfo: FuncFixtureInfo = fixtureinfo
         self.fixturenames = fixtureinfo.names_closure
         self._initrequest()
 
+    # todo: determine sound type limitations
     @classmethod
-    def from_parent(cls, parent, **kw):  # todo: determine sound type limitations
+    def from_parent(cls, parent, **kw) -> Self:
         """The public constructor."""
         return super().from_parent(parent=parent, **kw)
 
     def _initrequest(self) -> None:
-        self.funcargs: Dict[str, object] = {}
-        self._request = fixtures.FixtureRequest(self, _ispytest=True)
+        self.funcargs: dict[str, object] = {}
+        self._request = fixtures.TopRequest(self, _ispytest=True)
 
     @property
     def function(self):
@@ -1685,19 +1627,29 @@ def function(self):
 
     @property
     def instance(self):
-        """Python instance object the function is bound to.
-
-        Returns None if not a test method, e.g. for a standalone test function
-        or a staticmethod.
-        """
-        return getattr(self.obj, "__self__", None)
+        try:
+            return self._instance
+        except AttributeError:
+            if isinstance(self.parent, Class):
+                # Each Function gets a fresh class instance.
+                self._instance = self._getinstance()
+            else:
+                self._instance = None
+        return self._instance
 
-    def _getobj(self):
-        assert self.parent is not None
+    def _getinstance(self):
         if isinstance(self.parent, Class):
             # Each Function gets a fresh class instance.
-            parent_obj = self.parent.newinstance()
+            return self.parent.newinstance()
         else:
+            return None
+
+    def _getobj(self):
+        instance = self.instance
+        if instance is not None:
+            parent_obj = instance
+        else:
+            assert self.parent is not None
             parent_obj = self.parent.obj  # type: ignore[attr-defined]
         return getattr(parent_obj, self.originalname)
 
@@ -1713,7 +1665,7 @@ def runtest(self) -> None:
     def setup(self) -> None:
         self._request._fillfixtures()
 
-    def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
+    def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
         if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False):
             code = _pytest._code.Code.from_function(get_real_func(self.obj))
             path, firstlineno = code.path, code.firstlineno
@@ -1725,20 +1677,28 @@ def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
                     ntraceback = ntraceback.filter(filter_traceback)
                     if not ntraceback:
                         ntraceback = traceback
+            ntraceback = ntraceback.filter(excinfo)
 
-            excinfo.traceback = ntraceback.filter()
             # issue364: mark all but first and last frames to
             # only show a single-line message for each frame.
             if self.config.getoption("tbstyle", "auto") == "auto":
-                if len(excinfo.traceback) > 2:
-                    for entry in excinfo.traceback[1:-1]:
-                        entry.set_repr_style("short")
+                if len(ntraceback) > 2:
+                    ntraceback = Traceback(
+                        (
+                            ntraceback[0],
+                            *(t.with_repr_style("short") for t in ntraceback[1:-1]),
+                            ntraceback[-1],
+                        )
+                    )
+
+            return ntraceback
+        return excinfo.traceback
 
     # TODO: Type ignored -- breaks Liskov Substitution.
     def repr_failure(  # type: ignore[override]
         self,
         excinfo: ExceptionInfo[BaseException],
-    ) -> Union[str, TerminalRepr]:
+    ) -> str | TerminalRepr:
         style = self.config.getoption("tbstyle", "auto")
         if style == "auto":
             style = "long"
@@ -1746,10 +1706,8 @@ def repr_failure(  # type: ignore[override]
 
 
 class FunctionDefinition(Function):
-    """
-    This class is a step gap solution until we evolve to have actual function definition nodes
-    and manage to get rid of ``metafunc``.
-    """
+    """This class is a stop gap solution until we evolve to have actual function
+    definition nodes and manage to get rid of ``metafunc``."""
 
     def runtest(self) -> None:
         raise RuntimeError("function definitions are not supposed to be run as tests")
diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py
index 26f78c66ad4..77b0edc0ac5 100644
--- a/src/_pytest/python_api.py
+++ b/src/_pytest/python_api.py
@@ -1,53 +1,31 @@
-import math
-import pprint
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Collection
+from collections.abc import Mapping
+from collections.abc import Sequence
 from collections.abc import Sized
 from decimal import Decimal
+import math
 from numbers import Complex
-from types import TracebackType
+import pprint
+import sys
 from typing import Any
-from typing import Callable
-from typing import cast
-from typing import Generic
-from typing import Iterable
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import overload
-from typing import Pattern
-from typing import Sequence
-from typing import Tuple
-from typing import Type
 from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
+
 
 if TYPE_CHECKING:
     from numpy import ndarray
 
 
-import _pytest._code
-from _pytest.compat import final
-from _pytest.compat import STRING_TYPES
-from _pytest.outcomes import fail
-
-
-def _non_numeric_type_error(value, at: Optional[str]) -> TypeError:
-    at_str = f" at {at}" if at else ""
-    return TypeError(
-        "cannot make approximate comparisons to non-numeric values: {!r} {}".format(
-            value, at_str
-        )
-    )
-
-
 def _compare_approx(
     full_object: object,
-    message_data: Sequence[Tuple[str, str, str]],
+    message_data: Sequence[tuple[str, str, str]],
     number_of_elements: int,
     different_ids: Sequence[object],
     max_abs_diff: float,
     max_rel_diff: float,
-) -> List[str]:
+) -> list[str]:
     message_list = list(message_data)
     message_list.insert(0, ("Index", "Obtained", "Expected"))
     max_sizes = [0, 0, 0]
@@ -88,7 +66,7 @@ def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None:
     def __repr__(self) -> str:
         raise NotImplementedError
 
-    def _repr_compare(self, other_side: Any) -> List[str]:
+    def _repr_compare(self, other_side: Any) -> list[str]:
         return [
             "comparison failed",
             f"Obtained: {other_side}",
@@ -101,6 +79,7 @@ def __eq__(self, actual) -> bool:
         )
 
     def __bool__(self):
+        __tracebackhide__ = True
         raise AssertionError(
             "approx() is not supported in a boolean context.\nDid you mean: `assert a == approx(b)`?"
         )
@@ -111,7 +90,7 @@ def __bool__(self):
     def __ne__(self, actual) -> bool:
         return not (actual == self)
 
-    def _approx_scalar(self, x) -> "ApproxScalar":
+    def _approx_scalar(self, x) -> ApproxScalar:
         if isinstance(x, Decimal):
             return ApproxDecimal(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
         return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
@@ -130,12 +109,15 @@ def _check_type(self) -> None:
         # a numeric type.  For this reason, the default is to do nothing.  The
         # classes that deal with sequences should reimplement this method to
         # raise if there are any non-numeric elements in the sequence.
-        pass
 
 
-def _recursive_list_map(f, x):
-    if isinstance(x, list):
-        return [_recursive_list_map(f, xi) for xi in x]
+def _recursive_sequence_map(f, x):
+    """Recursively map a function over a sequence of arbitrary depth"""
+    if isinstance(x, (list, tuple)):
+        seq_type = type(x)
+        return seq_type(_recursive_sequence_map(f, xi) for xi in x)
+    elif _is_sequence_like(x):
+        return [_recursive_sequence_map(f, xi) for xi in x]
     else:
         return f(x)
 
@@ -144,15 +126,17 @@ class ApproxNumpy(ApproxBase):
     """Perform approximate comparisons where the expected value is numpy array."""
 
     def __repr__(self) -> str:
-        list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist())
+        list_scalars = _recursive_sequence_map(
+            self._approx_scalar, self.expected.tolist()
+        )
         return f"approx({list_scalars!r})"
 
-    def _repr_compare(self, other_side: "ndarray") -> List[str]:
+    def _repr_compare(self, other_side: ndarray | list[Any]) -> list[str]:
         import itertools
         import math
 
         def get_value_from_nested_list(
-            nested_list: List[Any], nd_index: Tuple[Any, ...]
+            nested_list: list[Any], nd_index: tuple[Any, ...]
         ) -> Any:
             """
             Helper function to get the value out of a nested list, given an n-dimensional index.
@@ -164,14 +148,18 @@ def get_value_from_nested_list(
             return value
 
         np_array_shape = self.expected.shape
-        approx_side_as_list = _recursive_list_map(
+        approx_side_as_seq = _recursive_sequence_map(
             self._approx_scalar, self.expected.tolist()
         )
 
-        if np_array_shape != other_side.shape:
+        # convert other_side to numpy array to ensure shape attribute is available
+        other_side_as_array = _as_numpy_array(other_side)
+        assert other_side_as_array is not None
+
+        if np_array_shape != other_side_as_array.shape:
             return [
                 "Impossible to compare arrays with different shapes.",
-                f"Shapes: {np_array_shape} and {other_side.shape}",
+                f"Shapes: {np_array_shape} and {other_side_as_array.shape}",
             ]
 
         number_of_elements = self.expected.size
@@ -179,8 +167,8 @@ def get_value_from_nested_list(
         max_rel_diff = -math.inf
         different_ids = []
         for index in itertools.product(*(range(i) for i in np_array_shape)):
-            approx_value = get_value_from_nested_list(approx_side_as_list, index)
-            other_value = get_value_from_nested_list(other_side, index)
+            approx_value = get_value_from_nested_list(approx_side_as_seq, index)
+            other_value = get_value_from_nested_list(other_side_as_array, index)
             if approx_value != other_value:
                 abs_diff = abs(approx_value.expected - other_value)
                 max_abs_diff = max(max_abs_diff, abs_diff)
@@ -193,8 +181,8 @@ def get_value_from_nested_list(
         message_data = [
             (
                 str(index),
-                str(get_value_from_nested_list(other_side, index)),
-                str(get_value_from_nested_list(approx_side_as_list, index)),
+                str(get_value_from_nested_list(other_side_as_array, index)),
+                str(get_value_from_nested_list(approx_side_as_seq, index)),
             )
             for index in different_ids
         ]
@@ -243,11 +231,9 @@ class ApproxMapping(ApproxBase):
     with numeric values (the keys can be anything)."""
 
     def __repr__(self) -> str:
-        return "approx({!r})".format(
-            {k: self._approx_scalar(v) for k, v in self.expected.items()}
-        )
+        return f"approx({ ({k: self._approx_scalar(v) for k, v in self.expected.items()})!r})"
 
-    def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]:
+    def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]:
         import math
 
         approx_side_as_map = {
@@ -262,13 +248,23 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]:
             approx_side_as_map.items(), other_side.values()
         ):
             if approx_value != other_value:
-                max_abs_diff = max(
-                    max_abs_diff, abs(approx_value.expected - other_value)
-                )
-                max_rel_diff = max(
-                    max_rel_diff,
-                    abs((approx_value.expected - other_value) / approx_value.expected),
-                )
+                if approx_value.expected is not None and other_value is not None:
+                    try:
+                        max_abs_diff = max(
+                            max_abs_diff, abs(approx_value.expected - other_value)
+                        )
+                        if approx_value.expected == 0.0:
+                            max_rel_diff = math.inf
+                        else:
+                            max_rel_diff = max(
+                                max_rel_diff,
+                                abs(
+                                    (approx_value.expected - other_value)
+                                    / approx_value.expected
+                                ),
+                            )
+                    except ZeroDivisionError:
+                        pass
                 different_ids.append(approx_key)
 
         message_data = [
@@ -306,20 +302,17 @@ def _check_type(self) -> None:
                 raise TypeError(msg.format(key, value, pprint.pformat(self.expected)))
 
 
-class ApproxSequencelike(ApproxBase):
+class ApproxSequenceLike(ApproxBase):
     """Perform approximate comparisons where the expected value is a sequence of numbers."""
 
     def __repr__(self) -> str:
         seq_type = type(self.expected)
-        if seq_type not in (tuple, list, set):
+        if seq_type not in (tuple, list):
             seq_type = list
-        return "approx({!r})".format(
-            seq_type(self._approx_scalar(x) for x in self.expected)
-        )
+        return f"approx({seq_type(self._approx_scalar(x) for x in self.expected)!r})"
 
-    def _repr_compare(self, other_side: Sequence[float]) -> List[str]:
+    def _repr_compare(self, other_side: Sequence[float]) -> list[str]:
         import math
-        import numpy as np
 
         if len(self.expected) != len(other_side):
             return [
@@ -327,7 +320,7 @@ def _repr_compare(self, other_side: Sequence[float]) -> List[str]:
                 f"Lengths: {len(self.expected)} and {len(other_side)}",
             ]
 
-        approx_side_as_map = _recursive_list_map(self._approx_scalar, self.expected)
+        approx_side_as_map = _recursive_sequence_map(self._approx_scalar, self.expected)
 
         number_of_elements = len(approx_side_as_map)
         max_abs_diff = -math.inf
@@ -337,14 +330,18 @@ def _repr_compare(self, other_side: Sequence[float]) -> List[str]:
             zip(approx_side_as_map, other_side)
         ):
             if approx_value != other_value:
-                abs_diff = abs(approx_value.expected - other_value)
-                max_abs_diff = max(max_abs_diff, abs_diff)
-                if other_value == 0.0:
-                    max_rel_diff = np.inf
+                try:
+                    abs_diff = abs(approx_value.expected - other_value)
+                    max_abs_diff = max(max_abs_diff, abs_diff)
+                # Ignore non-numbers for the diff calculations (#13012).
+                except TypeError:
+                    pass
                 else:
-                    max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value))
+                    if other_value == 0.0:
+                        max_rel_diff = math.inf
+                    else:
+                        max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value))
                 different_ids.append(i)
-
         message_data = [
             (str(i), str(other_side[i]), str(approx_side_as_map[i]))
             for i in different_ids
@@ -383,8 +380,8 @@ class ApproxScalar(ApproxBase):
 
     # Using Real should be better than this Union, but not possible yet:
     # https://github.com/python/typeshed/pull/3108
-    DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12
-    DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6
+    DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12
+    DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6
 
     def __repr__(self) -> str:
         """Return a string communicating both the expected value and the
@@ -395,15 +392,21 @@ def __repr__(self) -> str:
         # Don't show a tolerance for values that aren't compared using
         # tolerances, i.e. non-numerics and infinities. Need to call abs to
         # handle complex numbers, e.g. (inf + 1j).
-        if (not isinstance(self.expected, (Complex, Decimal))) or math.isinf(
-            abs(self.expected)  # type: ignore[arg-type]
+        if (
+            isinstance(self.expected, bool)
+            or (not isinstance(self.expected, (Complex, Decimal)))
+            or math.isinf(abs(self.expected) or isinstance(self.expected, bool))
         ):
             return str(self.expected)
 
         # If a sensible tolerance can't be calculated, self.tolerance will
         # raise a ValueError.  In this case, display '???'.
         try:
-            vetted_tolerance = f"{self.tolerance:.1e}"
+            if 1e-3 <= self.tolerance < 1e3:
+                vetted_tolerance = f"{self.tolerance:n}"
+            else:
+                vetted_tolerance = f"{self.tolerance:.1e}"
+
             if (
                 isinstance(self.expected, Complex)
                 and self.expected.imag
@@ -418,20 +421,35 @@ def __repr__(self) -> str:
     def __eq__(self, actual) -> bool:
         """Return whether the given value is equal to the expected value
         within the pre-specified tolerance."""
+
+        def is_bool(val: Any) -> bool:
+            # Check if `val` is a native bool or numpy bool.
+            if isinstance(val, bool):
+                return True
+            try:
+                import numpy as np
+
+                return isinstance(val, np.bool_)
+            except ImportError:
+                return False
+
         asarray = _as_numpy_array(actual)
         if asarray is not None:
             # Call ``__eq__()`` manually to prevent infinite-recursion with
             # numpy<1.13.  See #3748.
             return all(self.__eq__(a) for a in asarray.flat)
 
-        # Short-circuit exact equality.
-        if actual == self.expected:
+        # Short-circuit exact equality, except for bool and np.bool_
+        if is_bool(self.expected) and not is_bool(actual):
+            return False
+        elif actual == self.expected:
             return True
 
         # If either type is non-numeric, fall back to strict equality.
         # NB: we need Complex, rather than just Number, to ensure that __abs__,
-        # __sub__, and __float__ are defined.
-        if not (
+        # __sub__, and __float__ are defined. Also, consider bool to be
+        # non-numeric, even though it has the required arithmetic.
+        if is_bool(self.expected) or not (
             isinstance(self.expected, (Complex, Decimal))
             and isinstance(actual, (Complex, Decimal))
         ):
@@ -440,8 +458,8 @@ def __eq__(self, actual) -> bool:
         # Allow the user to control whether NaNs are considered equal to each
         # other or not.  The abs() calls are for compatibility with complex
         # numbers.
-        if math.isnan(abs(self.expected)):  # type: ignore[arg-type]
-            return self.nan_ok and math.isnan(abs(actual))  # type: ignore[arg-type]
+        if math.isnan(abs(self.expected)):
+            return self.nan_ok and math.isnan(abs(actual))
 
         # Infinity shouldn't be approximately equal to anything but itself, but
         # if there's a relative tolerance, it will be infinite and infinity
@@ -449,7 +467,7 @@ def __eq__(self, actual) -> bool:
         # case would have been short circuited above, so here we can just
         # return false if the expected value is infinite.  The abs() call is
         # for compatibility with complex numbers.
-        if math.isinf(abs(self.expected)):  # type: ignore[arg-type]
+        if math.isinf(abs(self.expected)):
             return False
 
         # Return true if the two numbers are within the tolerance.
@@ -515,10 +533,10 @@ class ApproxDecimal(ApproxScalar):
 
 
 def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
-    """Assert that two numbers (or two sets of numbers) are equal to each other
+    """Assert that two numbers (or two ordered sequences of numbers) are equal to each other
     within some tolerance.
 
-    Due to the :std:doc:`tutorial/floatingpoint`, numbers that we
+    Due to the :doc:`python:tutorial/floatingpoint`, numbers that we
     would intuitively expect to be equal are not always so::
 
         >>> 0.1 + 0.2 == 0.3
@@ -547,16 +565,11 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
         >>> 0.1 + 0.2 == approx(0.3)
         True
 
-    The same syntax also works for sequences of numbers::
+    The same syntax also works for ordered sequences of numbers::
 
         >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
         True
 
-    Dictionary *values*::
-
-        >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
-        True
-
     ``numpy`` arrays::
 
         >>> import numpy as np                                                          # doctest: +SKIP
@@ -569,6 +582,20 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
         >>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
         True
 
+    Only ordered sequences are supported, because ``approx`` needs
+    to infer the relative position of the sequences without ambiguity. This means
+    ``sets`` and other unordered sequences are not supported.
+
+    Finally, dictionary *values* can also be compared::
+
+        >>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
+        True
+
+    The comparison will be true if both mappings have the same keys and their
+    respective values match the expected tolerances.
+
+    **Tolerances**
+
     By default, ``approx`` considers numbers within a relative tolerance of
     ``1e-6`` (i.e. one part in a million) of its expected value to be equal.
     This treatment would lead to surprising results if the expected value was
@@ -605,8 +632,10 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
         >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
         True
 
-    You can also use ``approx`` to compare nonnumeric types, or dicts and
-    sequences containing nonnumeric types, in which case it falls back to
+    **Non-numeric types**
+
+    You can also use ``approx`` to compare non-numeric types, or dicts and
+    sequences containing non-numeric types, in which case it falls back to
     strict equality. This can be useful for comparing dicts and sequences that
     can contain optional values::
 
@@ -658,6 +687,20 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
         specialised test helpers in :std:doc:`numpy:reference/routines.testing`
         if you need support for comparisons, NaNs, or ULP-based tolerances.
 
+        To match strings using regex, you can use
+        `Matches <https://github.com/asottile/re-assert#re_assertmatchespattern-str-args-kwargs>`_
+        from the
+        `re_assert package <https://github.com/asottile/re-assert>`_.
+
+
+    .. note::
+
+        Unlike built-in equality, this function considers
+        booleans unequal to numeric zero or one. For example::
+
+           >>> 1 == approx(True)
+           False
+
     .. warning::
 
        .. versionchanged:: 3.2
@@ -676,13 +719,12 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
 
     .. versionchanged:: 3.7.1
        ``approx`` raises ``TypeError`` when it encounters a dict value or
-       sequence element of nonnumeric type.
+       sequence element of non-numeric type.
 
     .. versionchanged:: 6.1.0
-       ``approx`` falls back to strict equality for nonnumeric types instead
+       ``approx`` falls back to strict equality for non-numeric types instead
        of raising ``TypeError``.
     """
-
     # Delegate the comparison to a class that knows how to deal with the type
     # of the expected value (e.g. int, float, list, dict, numpy.array, etc).
     #
@@ -701,25 +743,31 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
     __tracebackhide__ = True
 
     if isinstance(expected, Decimal):
-        cls: Type[ApproxBase] = ApproxDecimal
+        cls: type[ApproxBase] = ApproxDecimal
     elif isinstance(expected, Mapping):
         cls = ApproxMapping
     elif _is_numpy_array(expected):
         expected = _as_numpy_array(expected)
         cls = ApproxNumpy
-    elif (
-        isinstance(expected, Iterable)
-        and isinstance(expected, Sized)
-        # Type ignored because the error is wrong -- not unreachable.
-        and not isinstance(expected, STRING_TYPES)  # type: ignore[unreachable]
-    ):
-        cls = ApproxSequencelike
+    elif _is_sequence_like(expected):
+        cls = ApproxSequenceLike
+    elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)):
+        msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}"
+        raise TypeError(msg)
     else:
         cls = ApproxScalar
 
     return cls(expected, rel, abs, nan_ok)
 
 
+def _is_sequence_like(expected: object) -> bool:
+    return (
+        hasattr(expected, "__getitem__")
+        and isinstance(expected, Sized)
+        and not isinstance(expected, (str, bytes))
+    )
+
+
 def _is_numpy_array(obj: object) -> bool:
     """
     Return true if the given object is implicitly convertible to ndarray,
@@ -728,13 +776,11 @@ def _is_numpy_array(obj: object) -> bool:
     return _as_numpy_array(obj) is not None
 
 
-def _as_numpy_array(obj: object) -> Optional["ndarray"]:
+def _as_numpy_array(obj: object) -> ndarray | None:
     """
     Return an ndarray if the given object is implicitly convertible to ndarray,
     and numpy is already imported, otherwise None.
     """
-    import sys
-
     np: Any = sys.modules.get("numpy")
     if np is not None:
         # avoid infinite recursion on numpy scalars, which have __array__
@@ -745,216 +791,3 @@ def _as_numpy_array(obj: object) -> Optional["ndarray"]:
         elif hasattr(obj, "__array__") or hasattr("obj", "__array_interface__"):
             return np.asarray(obj)
     return None
-
-
-# builtin pytest.raises helper
-
-E = TypeVar("E", bound=BaseException)
-
-
-@overload
-def raises(
-    expected_exception: Union[Type[E], Tuple[Type[E], ...]],
-    *,
-    match: Optional[Union[str, Pattern[str]]] = ...,
-) -> "RaisesContext[E]":
-    ...
-
-
-@overload
-def raises(
-    expected_exception: Union[Type[E], Tuple[Type[E], ...]],
-    func: Callable[..., Any],
-    *args: Any,
-    **kwargs: Any,
-) -> _pytest._code.ExceptionInfo[E]:
-    ...
-
-
-def raises(
-    expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any
-) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]:
-    r"""Assert that a code block/function call raises ``expected_exception``
-    or raise a failure exception otherwise.
-
-    :kwparam match:
-        If specified, a string containing a regular expression,
-        or a regular expression object, that is tested against the string
-        representation of the exception using :py:func:`re.search`. To match a literal
-        string that may contain :std:ref:`special characters <re-syntax>`, the pattern can
-        first be escaped with :py:func:`re.escape`.
-
-        (This is only used when :py:func:`pytest.raises` is used as a context manager,
-        and passed through to the function otherwise.
-        When using :py:func:`pytest.raises` as a function, you can use:
-        ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.)
-
-    .. currentmodule:: _pytest._code
-
-    Use ``pytest.raises`` as a context manager, which will capture the exception of the given
-    type::
-
-        >>> import pytest
-        >>> with pytest.raises(ZeroDivisionError):
-        ...    1/0
-
-    If the code block does not raise the expected exception (``ZeroDivisionError`` in the example
-    above), or no exception at all, the check will fail instead.
-
-    You can also use the keyword argument ``match`` to assert that the
-    exception matches a text or regex::
-
-        >>> with pytest.raises(ValueError, match='must be 0 or None'):
-        ...     raise ValueError("value must be 0 or None")
-
-        >>> with pytest.raises(ValueError, match=r'must be \d+$'):
-        ...     raise ValueError("value must be 42")
-
-    The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
-    details of the captured exception::
-
-        >>> with pytest.raises(ValueError) as exc_info:
-        ...     raise ValueError("value must be 42")
-        >>> assert exc_info.type is ValueError
-        >>> assert exc_info.value.args[0] == "value must be 42"
-
-    .. note::
-
-       When using ``pytest.raises`` as a context manager, it's worthwhile to
-       note that normal context manager rules apply and that the exception
-       raised *must* be the final line in the scope of the context manager.
-       Lines of code after that, within the scope of the context manager will
-       not be executed. For example::
-
-           >>> value = 15
-           >>> with pytest.raises(ValueError) as exc_info:
-           ...     if value > 10:
-           ...         raise ValueError("value must be <= 10")
-           ...     assert exc_info.type is ValueError  # this will not execute
-
-       Instead, the following approach must be taken (note the difference in
-       scope)::
-
-           >>> with pytest.raises(ValueError) as exc_info:
-           ...     if value > 10:
-           ...         raise ValueError("value must be <= 10")
-           ...
-           >>> assert exc_info.type is ValueError
-
-    **Using with** ``pytest.mark.parametrize``
-
-    When using :ref:`pytest.mark.parametrize ref`
-    it is possible to parametrize tests such that
-    some runs raise an exception and others do not.
-
-    See :ref:`parametrizing_conditional_raising` for an example.
-
-    **Legacy form**
-
-    It is possible to specify a callable by passing a to-be-called lambda::
-
-        >>> raises(ZeroDivisionError, lambda: 1/0)
-        <ExceptionInfo ...>
-
-    or you can specify an arbitrary callable with arguments::
-
-        >>> def f(x): return 1/x
-        ...
-        >>> raises(ZeroDivisionError, f, 0)
-        <ExceptionInfo ...>
-        >>> raises(ZeroDivisionError, f, x=0)
-        <ExceptionInfo ...>
-
-    The form above is fully supported but discouraged for new code because the
-    context manager form is regarded as more readable and less error-prone.
-
-    .. note::
-        Similar to caught exception objects in Python, explicitly clearing
-        local references to returned ``ExceptionInfo`` objects can
-        help the Python interpreter speed up its garbage collection.
-
-        Clearing those references breaks a reference cycle
-        (``ExceptionInfo`` --> caught exception --> frame stack raising
-        the exception --> current frame stack --> local variables -->
-        ``ExceptionInfo``) which makes Python keep all objects referenced
-        from that cycle (including all local variables in the current
-        frame) alive until the next cyclic garbage collection run.
-        More detailed information can be found in the official Python
-        documentation for :ref:`the try statement <python:try>`.
-    """
-    __tracebackhide__ = True
-
-    if isinstance(expected_exception, type):
-        excepted_exceptions: Tuple[Type[E], ...] = (expected_exception,)
-    else:
-        excepted_exceptions = expected_exception
-    for exc in excepted_exceptions:
-        if not isinstance(exc, type) or not issubclass(exc, BaseException):
-            msg = "expected exception must be a BaseException type, not {}"  # type: ignore[unreachable]
-            not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__
-            raise TypeError(msg.format(not_a))
-
-    message = f"DID NOT RAISE {expected_exception}"
-
-    if not args:
-        match: Optional[Union[str, Pattern[str]]] = kwargs.pop("match", None)
-        if kwargs:
-            msg = "Unexpected keyword arguments passed to pytest.raises: "
-            msg += ", ".join(sorted(kwargs))
-            msg += "\nUse context-manager form instead?"
-            raise TypeError(msg)
-        return RaisesContext(expected_exception, message, match)
-    else:
-        func = args[0]
-        if not callable(func):
-            raise TypeError(f"{func!r} object (type: {type(func)}) must be callable")
-        try:
-            func(*args[1:], **kwargs)
-        except expected_exception as e:
-            # We just caught the exception - there is a traceback.
-            assert e.__traceback__ is not None
-            return _pytest._code.ExceptionInfo.from_exc_info(
-                (type(e), e, e.__traceback__)
-            )
-    fail(message)
-
-
-# This doesn't work with mypy for now. Use fail.Exception instead.
-raises.Exception = fail.Exception  # type: ignore
-
-
-@final
-class RaisesContext(Generic[E]):
-    def __init__(
-        self,
-        expected_exception: Union[Type[E], Tuple[Type[E], ...]],
-        message: str,
-        match_expr: Optional[Union[str, Pattern[str]]] = None,
-    ) -> None:
-        self.expected_exception = expected_exception
-        self.message = message
-        self.match_expr = match_expr
-        self.excinfo: Optional[_pytest._code.ExceptionInfo[E]] = None
-
-    def __enter__(self) -> _pytest._code.ExceptionInfo[E]:
-        self.excinfo = _pytest._code.ExceptionInfo.for_later()
-        return self.excinfo
-
-    def __exit__(
-        self,
-        exc_type: Optional[Type[BaseException]],
-        exc_val: Optional[BaseException],
-        exc_tb: Optional[TracebackType],
-    ) -> bool:
-        __tracebackhide__ = True
-        if exc_type is None:
-            fail(self.message)
-        assert self.excinfo is not None
-        if not issubclass(exc_type, self.expected_exception):
-            return False
-        # Cast to narrow the exception type now that it's verified.
-        exc_info = cast(Tuple[Type[E], E, TracebackType], (exc_type, exc_val, exc_tb))
-        self.excinfo.fill_unfilled(exc_info)
-        if self.match_expr is not None:
-            self.excinfo.match(self.match_expr)
-        return True
diff --git a/src/_pytest/pythonpath.py b/src/_pytest/pythonpath.py
deleted file mode 100644
index cceabbca12a..00000000000
--- a/src/_pytest/pythonpath.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import sys
-
-import pytest
-from pytest import Config
-from pytest import Parser
-
-
-def pytest_addoption(parser: Parser) -> None:
-    parser.addini("pythonpath", type="paths", help="Add paths to sys.path", default=[])
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_load_initial_conftests(early_config: Config) -> None:
-    # `pythonpath = a b` will set `sys.path` to `[a, b, x, y, z, ...]`
-    for path in reversed(early_config.getini("pythonpath")):
-        sys.path.insert(0, str(path))
-
-
-@pytest.hookimpl(trylast=True)
-def pytest_unconfigure(config: Config) -> None:
-    for path in config.getini("pythonpath"):
-        path_str = str(path)
-        if path_str in sys.path:
-            sys.path.remove(path_str)
diff --git a/src/_pytest/raises.py b/src/_pytest/raises.py
new file mode 100644
index 00000000000..2eba53bf10b
--- /dev/null
+++ b/src/_pytest/raises.py
@@ -0,0 +1,1521 @@
+from __future__ import annotations
+
+from abc import ABC
+from abc import abstractmethod
+import re
+from re import Pattern
+import sys
+from textwrap import indent
+from typing import Any
+from typing import cast
+from typing import final
+from typing import Generic
+from typing import get_args
+from typing import get_origin
+from typing import Literal
+from typing import overload
+from typing import TYPE_CHECKING
+import warnings
+
+from _pytest._code import ExceptionInfo
+from _pytest._code.code import stringify_exception
+from _pytest.outcomes import fail
+from _pytest.warning_types import PytestWarning
+
+
+if TYPE_CHECKING:
+    from collections.abc import Callable
+    from collections.abc import Sequence
+
+    # for some reason Sphinx does not play well with 'from types import TracebackType'
+    import types
+
+    from typing_extensions import ParamSpec
+    from typing_extensions import TypeGuard
+    from typing_extensions import TypeVar
+
+    P = ParamSpec("P")
+
+    # this conditional definition is because we want to allow a TypeVar default
+    BaseExcT_co_default = TypeVar(
+        "BaseExcT_co_default",
+        bound=BaseException,
+        default=BaseException,
+        covariant=True,
+    )
+
+    # Use short name because it shows up in docs.
+    E = TypeVar("E", bound=BaseException, default=BaseException)
+else:
+    from typing import TypeVar
+
+    BaseExcT_co_default = TypeVar(
+        "BaseExcT_co_default", bound=BaseException, covariant=True
+    )
+
+# RaisesGroup doesn't work with a default.
+BaseExcT_co = TypeVar("BaseExcT_co", bound=BaseException, covariant=True)
+BaseExcT_1 = TypeVar("BaseExcT_1", bound=BaseException)
+BaseExcT_2 = TypeVar("BaseExcT_2", bound=BaseException)
+ExcT_1 = TypeVar("ExcT_1", bound=Exception)
+ExcT_2 = TypeVar("ExcT_2", bound=Exception)
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import BaseExceptionGroup
+    from exceptiongroup import ExceptionGroup
+
+
+# String patterns default to including the unicode flag.
+_REGEX_NO_FLAGS = re.compile(r"").flags
+
+
+# pytest.raises helper
+@overload
+def raises(
+    expected_exception: type[E] | tuple[type[E], ...],
+    *,
+    match: str | re.Pattern[str] | None = ...,
+    check: Callable[[E], bool] = ...,
+) -> RaisesExc[E]: ...
+
+
+@overload
+def raises(
+    *,
+    match: str | re.Pattern[str],
+    # If exception_type is not provided, check() must do any typechecks itself.
+    check: Callable[[BaseException], bool] = ...,
+) -> RaisesExc[BaseException]: ...
+
+
+@overload
+def raises(*, check: Callable[[BaseException], bool]) -> RaisesExc[BaseException]: ...
+
+
+@overload
+def raises(
+    expected_exception: type[E] | tuple[type[E], ...],
+    func: Callable[..., Any],
+    *args: Any,
+    **kwargs: Any,
+) -> ExceptionInfo[E]: ...
+
+
+def raises(
+    expected_exception: type[E] | tuple[type[E], ...] | None = None,
+    *args: Any,
+    **kwargs: Any,
+) -> RaisesExc[BaseException] | ExceptionInfo[E]:
+    r"""Assert that a code block/function call raises an exception type, or one of its subclasses.
+
+    :param expected_exception:
+        The expected exception type, or a tuple if one of multiple possible
+        exception types are expected. Note that subclasses of the passed exceptions
+        will also match.
+
+        This is not a required parameter, you may opt to only use ``match`` and/or
+        ``check`` for verifying the raised exception.
+
+    :kwparam str | re.Pattern[str] | None match:
+        If specified, a string containing a regular expression,
+        or a regular expression object, that is tested against the string
+        representation of the exception and its :pep:`678` `__notes__`
+        using :func:`re.search`.
+
+        To match a literal string that may contain :ref:`special characters
+        <re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
+
+        (This is only used when ``pytest.raises`` is used as a context manager,
+        and passed through to the function otherwise.
+        When using ``pytest.raises`` as a function, you can use:
+        ``pytest.raises(Exc, func, match="passed on").match("my pattern")``.)
+
+    :kwparam Callable[[BaseException], bool] check:
+
+        .. versionadded:: 8.4
+
+        If specified, a callable that will be called with the exception as a parameter
+        after checking the type and the match regex if specified.
+        If it returns ``True`` it will be considered a match, if not it will
+        be considered a failed match.
+
+
+    Use ``pytest.raises`` as a context manager, which will capture the exception of the given
+    type, or any of its subclasses::
+
+        >>> import pytest
+        >>> with pytest.raises(ZeroDivisionError):
+        ...    1/0
+
+    If the code block does not raise the expected exception (:class:`ZeroDivisionError` in the example
+    above), or no exception at all, the check will fail instead.
+
+    You can also use the keyword argument ``match`` to assert that the
+    exception matches a text or regex::
+
+        >>> with pytest.raises(ValueError, match='must be 0 or None'):
+        ...     raise ValueError("value must be 0 or None")
+
+        >>> with pytest.raises(ValueError, match=r'must be \d+$'):
+        ...     raise ValueError("value must be 42")
+
+    The ``match`` argument searches the formatted exception string, which includes any
+    `PEP-678 <https://peps.python.org/pep-0678/>`__ ``__notes__``:
+
+        >>> with pytest.raises(ValueError, match=r"had a note added"):  # doctest: +SKIP
+        ...     e = ValueError("value must be 42")
+        ...     e.add_note("had a note added")
+        ...     raise e
+
+    The ``check`` argument, if provided, must return True when passed the raised exception
+    for the match to be successful, otherwise an :exc:`AssertionError` is raised.
+
+        >>> import errno
+        >>> with pytest.raises(OSError, check=lambda e: e.errno == errno.EACCES):
+        ...     raise OSError(errno.EACCES, "no permission to view")
+
+    The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
+    details of the captured exception::
+
+        >>> with pytest.raises(ValueError) as exc_info:
+        ...     raise ValueError("value must be 42")
+        >>> assert exc_info.type is ValueError
+        >>> assert exc_info.value.args[0] == "value must be 42"
+
+    .. warning::
+
+       Given that ``pytest.raises`` matches subclasses, be wary of using it to match :class:`Exception` like this::
+
+           # Careful, this will catch ANY exception raised.
+           with pytest.raises(Exception):
+               some_function()
+
+       Because :class:`Exception` is the base class of almost all exceptions, it is easy for this to hide
+       real bugs, where the user wrote this expecting a specific exception, but some other exception is being
+       raised due to a bug introduced during a refactoring.
+
+       Avoid using ``pytest.raises`` to catch :class:`Exception` unless certain that you really want to catch
+       **any** exception raised.
+
+    .. note::
+
+       When using ``pytest.raises`` as a context manager, it's worthwhile to
+       note that normal context manager rules apply and that the exception
+       raised *must* be the final line in the scope of the context manager.
+       Lines of code after that, within the scope of the context manager will
+       not be executed. For example::
+
+           >>> value = 15
+           >>> with pytest.raises(ValueError) as exc_info:
+           ...     if value > 10:
+           ...         raise ValueError("value must be <= 10")
+           ...     assert exc_info.type is ValueError  # This will not execute.
+
+       Instead, the following approach must be taken (note the difference in
+       scope)::
+
+           >>> with pytest.raises(ValueError) as exc_info:
+           ...     if value > 10:
+           ...         raise ValueError("value must be <= 10")
+           ...
+           >>> assert exc_info.type is ValueError
+
+    **Expecting exception groups**
+
+    When expecting exceptions wrapped in :exc:`BaseExceptionGroup` or
+    :exc:`ExceptionGroup`, you should instead use :class:`pytest.RaisesGroup`.
+
+    **Using with** ``pytest.mark.parametrize``
+
+    When using :ref:`pytest.mark.parametrize ref`
+    it is possible to parametrize tests such that
+    some runs raise an exception and others do not.
+
+    See :ref:`parametrizing_conditional_raising` for an example.
+
+    .. seealso::
+
+        :ref:`assertraises` for more examples and detailed discussion.
+
+    **Legacy form**
+
+    It is possible to specify a callable by passing a to-be-called lambda::
+
+        >>> raises(ZeroDivisionError, lambda: 1/0)
+        <ExceptionInfo ...>
+
+    or you can specify an arbitrary callable with arguments::
+
+        >>> def f(x): return 1/x
+        ...
+        >>> raises(ZeroDivisionError, f, 0)
+        <ExceptionInfo ...>
+        >>> raises(ZeroDivisionError, f, x=0)
+        <ExceptionInfo ...>
+
+    The form above is fully supported but discouraged for new code because the
+    context manager form is regarded as more readable and less error-prone.
+
+    .. note::
+        Similar to caught exception objects in Python, explicitly clearing
+        local references to returned ``ExceptionInfo`` objects can
+        help the Python interpreter speed up its garbage collection.
+
+        Clearing those references breaks a reference cycle
+        (``ExceptionInfo`` --> caught exception --> frame stack raising
+        the exception --> current frame stack --> local variables -->
+        ``ExceptionInfo``) which makes Python keep all objects referenced
+        from that cycle (including all local variables in the current
+        frame) alive until the next cyclic garbage collection run.
+        More detailed information can be found in the official Python
+        documentation for :ref:`the try statement <python:try>`.
+    """
+    __tracebackhide__ = True
+
+    if not args:
+        if set(kwargs) - {"match", "check", "expected_exception"}:
+            msg = "Unexpected keyword arguments passed to pytest.raises: "
+            msg += ", ".join(sorted(kwargs))
+            msg += "\nUse context-manager form instead?"
+            raise TypeError(msg)
+
+        if expected_exception is None:
+            return RaisesExc(**kwargs)
+        return RaisesExc(expected_exception, **kwargs)
+
+    if not expected_exception:
+        raise ValueError(
+            f"Expected an exception type or a tuple of exception types, but got `{expected_exception!r}`. "
+            f"Raising exceptions is already understood as failing the test, so you don't need "
+            f"any special code to say 'this should never raise an exception'."
+        )
+    func = args[0]
+    if not callable(func):
+        raise TypeError(f"{func!r} object (type: {type(func)}) must be callable")
+    with RaisesExc(expected_exception) as excinfo:
+        func(*args[1:], **kwargs)
+    try:
+        return excinfo
+    finally:
+        del excinfo
+
+
+# note: RaisesExc/RaisesGroup uses fail() internally, so this alias
+#  indicates (to [internal] plugins?) that `pytest.raises` will
+#  raise `_pytest.outcomes.Failed`, where
+#  `outcomes.Failed is outcomes.fail.Exception is raises.Exception`
+# note: this is *not* the same as `_pytest.main.Failed`
+# note: mypy does not recognize this attribute, and it's not possible
+#  to use a protocol/decorator like the others in outcomes due to
+#  https://github.com/python/mypy/issues/18715
+raises.Exception = fail.Exception  # type: ignore[attr-defined]
+
+
+def _match_pattern(match: Pattern[str]) -> str | Pattern[str]:
+    """Helper function to remove redundant `re.compile` calls when printing regex"""
+    return match.pattern if match.flags == _REGEX_NO_FLAGS else match
+
+
+def repr_callable(fun: Callable[[BaseExcT_1], bool]) -> str:
+    """Get the repr of a ``check`` parameter.
+
+    Split out so it can be monkeypatched (e.g. by hypothesis)
+    """
+    return repr(fun)
+
+
+def backquote(s: str) -> str:
+    return "`" + s + "`"
+
+
+def _exception_type_name(
+    e: type[BaseException] | tuple[type[BaseException], ...],
+) -> str:
+    if isinstance(e, type):
+        return e.__name__
+    if len(e) == 1:
+        return e[0].__name__
+    return "(" + ", ".join(ee.__name__ for ee in e) + ")"
+
+
+def _check_raw_type(
+    expected_type: type[BaseException] | tuple[type[BaseException], ...] | None,
+    exception: BaseException,
+) -> str | None:
+    if expected_type is None or expected_type == ():
+        return None
+
+    if not isinstance(
+        exception,
+        expected_type,
+    ):
+        actual_type_str = backquote(_exception_type_name(type(exception)) + "()")
+        expected_type_str = backquote(_exception_type_name(expected_type))
+        if (
+            isinstance(exception, BaseExceptionGroup)
+            and isinstance(expected_type, type)
+            and not issubclass(expected_type, BaseExceptionGroup)
+        ):
+            return f"Unexpected nested {actual_type_str}, expected {expected_type_str}"
+        return f"{actual_type_str} is not an instance of {expected_type_str}"
+    return None
+
+
+def is_fully_escaped(s: str) -> bool:
+    # we know we won't compile with re.VERBOSE, so whitespace doesn't need to be escaped
+    metacharacters = "{}()+.*?^$[]"
+    return not any(
+        c in metacharacters and (i == 0 or s[i - 1] != "\\") for (i, c) in enumerate(s)
+    )
+
+
+def unescape(s: str) -> str:
+    return re.sub(r"\\([{}()+-.*?^$\[\]\s\\])", r"\1", s)
+
+
+# These classes conceptually differ from ExceptionInfo in that ExceptionInfo is tied, and
+# constructed from, a particular exception - whereas these are constructed with expected
+# exceptions, and later allow matching towards particular exceptions.
+# But there's overlap in `ExceptionInfo.match` and `AbstractRaises._check_match`, as with
+# `AbstractRaises.matches` and `ExceptionInfo.errisinstance`+`ExceptionInfo.group_contains`.
+# The interaction between these classes should perhaps be improved.
+class AbstractRaises(ABC, Generic[BaseExcT_co]):
+    """ABC with common functionality shared between RaisesExc and RaisesGroup"""
+
+    def __init__(
+        self,
+        *,
+        match: str | Pattern[str] | None,
+        check: Callable[[BaseExcT_co], bool] | None,
+    ) -> None:
+        if isinstance(match, str):
+            # juggle error in order to avoid context to fail (necessary?)
+            re_error = None
+            try:
+                self.match: Pattern[str] | None = re.compile(match)
+            except re.error as e:
+                re_error = e
+            if re_error is not None:
+                fail(f"Invalid regex pattern provided to 'match': {re_error}")
+            if match == "":
+                warnings.warn(
+                    PytestWarning(
+                        "matching against an empty string will *always* pass. If you want "
+                        "to check for an empty message you need to pass '^$'. If you don't "
+                        "want to match you should pass `None` or leave out the parameter."
+                    ),
+                    stacklevel=2,
+                )
+        else:
+            self.match = match
+
+        # check if this is a fully escaped regex and has ^$ to match fully
+        # in which case we can do a proper diff on error
+        self.rawmatch: str | None = None
+        if isinstance(match, str) or (
+            isinstance(match, Pattern) and match.flags == _REGEX_NO_FLAGS
+        ):
+            if isinstance(match, Pattern):
+                match = match.pattern
+            if (
+                match
+                and match[0] == "^"
+                and match[-1] == "$"
+                and is_fully_escaped(match[1:-1])
+            ):
+                self.rawmatch = unescape(match[1:-1])
+
+        self.check = check
+        self._fail_reason: str | None = None
+
+        # used to suppress repeated printing of `repr(self.check)`
+        self._nested: bool = False
+
+        # set in self._parse_exc
+        self.is_baseexception = False
+
+    def _parse_exc(
+        self, exc: type[BaseExcT_1] | types.GenericAlias, expected: str
+    ) -> type[BaseExcT_1]:
+        if isinstance(exc, type) and issubclass(exc, BaseException):
+            if not issubclass(exc, Exception):
+                self.is_baseexception = True
+            return exc
+        # because RaisesGroup does not support variable number of exceptions there's
+        # still a use for RaisesExc(ExceptionGroup[Exception]).
+        origin_exc: type[BaseException] | None = get_origin(exc)
+        if origin_exc and issubclass(origin_exc, BaseExceptionGroup):
+            exc_type = get_args(exc)[0]
+            if (
+                issubclass(origin_exc, ExceptionGroup) and exc_type in (Exception, Any)
+            ) or (
+                issubclass(origin_exc, BaseExceptionGroup)
+                and exc_type in (BaseException, Any)
+            ):
+                if not isinstance(exc, Exception):
+                    self.is_baseexception = True
+                return cast(type[BaseExcT_1], origin_exc)
+            else:
+                raise ValueError(
+                    f"Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseExeption]` "
+                    f"are accepted as generic types but got `{exc}`. "
+                    f"As `raises` will catch all instances of the specified group regardless of the "
+                    f"generic argument specific nested exceptions has to be checked "
+                    f"with `RaisesGroup`."
+                )
+        # unclear if the Type/ValueError distinction is even helpful here
+        msg = f"expected exception must be {expected}, not "
+        if isinstance(exc, type):
+            raise ValueError(msg + f"{exc.__name__!r}")
+        if isinstance(exc, BaseException):
+            raise TypeError(msg + f"an exception instance ({type(exc).__name__})")
+        raise TypeError(msg + repr(type(exc).__name__))
+
+    @property
+    def fail_reason(self) -> str | None:
+        """Set after a call to :meth:`matches` to give a human-readable reason for why the match failed.
+        When used as a context manager the string will be printed as the reason for the
+        test failing."""
+        return self._fail_reason
+
+    def _check_check(
+        self: AbstractRaises[BaseExcT_1],
+        exception: BaseExcT_1,
+    ) -> bool:
+        if self.check is None:
+            return True
+
+        if self.check(exception):
+            return True
+
+        check_repr = "" if self._nested else " " + repr_callable(self.check)
+        self._fail_reason = f"check{check_repr} did not return True"
+        return False
+
+    # TODO: harmonize with ExceptionInfo.match
+    def _check_match(self, e: BaseException) -> bool:
+        if self.match is None or re.search(
+            self.match,
+            stringified_exception := stringify_exception(
+                e, include_subexception_msg=False
+            ),
+        ):
+            return True
+
+        # if we're matching a group, make sure we're explicit to reduce confusion
+        # if they're trying to match an exception contained within the group
+        maybe_specify_type = (
+            f" the `{_exception_type_name(type(e))}()`"
+            if isinstance(e, BaseExceptionGroup)
+            else ""
+        )
+        if isinstance(self.rawmatch, str):
+            # TODO: it instructs to use `-v` to print leading text, but that doesn't work
+            # I also don't know if this is the proper entry point, or tool to use at all
+            from _pytest.assertion.util import _diff_text
+            from _pytest.assertion.util import dummy_highlighter
+
+            diff = _diff_text(self.rawmatch, stringified_exception, dummy_highlighter)
+            self._fail_reason = ("\n" if diff[0][0] == "-" else "") + "\n".join(diff)
+            return False
+
+        # I don't love "Regex"+"Input" vs something like "expected regex"+"exception message"
+        # when they're similar it's not always obvious which is which
+        self._fail_reason = (
+            f"Regex pattern did not match{maybe_specify_type}.\n"
+            f" Regex: {_match_pattern(self.match)!r}\n"
+            f" Input: {stringified_exception!r}"
+        )
+        if _match_pattern(self.match) == stringified_exception:
+            self._fail_reason += "\n Did you mean to `re.escape()` the regex?"
+        return False
+
+    @abstractmethod
+    def matches(
+        self: AbstractRaises[BaseExcT_1], exception: BaseException
+    ) -> TypeGuard[BaseExcT_1]:
+        """Check if an exception matches the requirements of this AbstractRaises.
+        If it fails, :meth:`AbstractRaises.fail_reason` should be set.
+        """
+
+
+@final
+class RaisesExc(AbstractRaises[BaseExcT_co_default]):
+    """
+    .. versionadded:: 8.4
+
+
+    This is the class constructed when calling :func:`pytest.raises`, but may be used
+    directly as a helper class with :class:`RaisesGroup` when you want to specify
+    requirements on sub-exceptions.
+
+    You don't need this if you only want to specify the type, since :class:`RaisesGroup`
+    accepts ``type[BaseException]``.
+
+    :param type[BaseException] | tuple[type[BaseException]] | None expected_exception:
+        The expected type, or one of several possible types.
+        May be ``None`` in order to only make use of ``match`` and/or ``check``
+
+        The type is checked with :func:`isinstance`, and does not need to be an exact match.
+        If that is wanted you can use the ``check`` parameter.
+
+    :kwparam str | Pattern[str] match
+        A regex to match.
+
+    :kwparam Callable[[BaseException], bool] check:
+        If specified, a callable that will be called with the exception as a parameter
+        after checking the type and the match regex if specified.
+        If it returns ``True`` it will be considered a match, if not it will
+        be considered a failed match.
+
+    :meth:`RaisesExc.matches` can also be used standalone to check individual exceptions.
+
+    Examples::
+
+        with RaisesGroup(RaisesExc(ValueError, match="string"))
+            ...
+        with RaisesGroup(RaisesExc(check=lambda x: x.args == (3, "hello"))):
+            ...
+        with RaisesGroup(RaisesExc(check=lambda x: type(x) is ValueError)):
+            ...
+    """
+
+    # Trio bundled hypothesis monkeypatching, we will probably instead assume that
+    # hypothesis will handle that in their pytest plugin by the time this is released.
+    # Alternatively we could add a version of get_pretty_function_description ourselves
+    # https://github.com/HypothesisWorks/hypothesis/blob/8ced2f59f5c7bea3344e35d2d53e1f8f8eb9fcd8/hypothesis-python/src/hypothesis/internal/reflection.py#L439
+
+    # At least one of the three parameters must be passed.
+    @overload
+    def __init__(
+        self: RaisesExc[BaseExcT_co_default],
+        expected_exception: (
+            type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...]
+        ),
+        /,
+        *,
+        match: str | Pattern[str] | None = ...,
+        check: Callable[[BaseExcT_co_default], bool] | None = ...,
+    ) -> None: ...
+
+    @overload
+    def __init__(
+        self: RaisesExc[BaseException],  # Give E a value.
+        /,
+        *,
+        match: str | Pattern[str] | None,
+        # If exception_type is not provided, check() must do any typechecks itself.
+        check: Callable[[BaseException], bool] | None = ...,
+    ) -> None: ...
+
+    @overload
+    def __init__(self, /, *, check: Callable[[BaseException], bool]) -> None: ...
+
+    def __init__(
+        self,
+        expected_exception: (
+            type[BaseExcT_co_default] | tuple[type[BaseExcT_co_default], ...] | None
+        ) = None,
+        /,
+        *,
+        match: str | Pattern[str] | None = None,
+        check: Callable[[BaseExcT_co_default], bool] | None = None,
+    ):
+        super().__init__(match=match, check=check)
+        if isinstance(expected_exception, tuple):
+            expected_exceptions = expected_exception
+        elif expected_exception is None:
+            expected_exceptions = ()
+        else:
+            expected_exceptions = (expected_exception,)
+
+        if (expected_exceptions == ()) and match is None and check is None:
+            raise ValueError("You must specify at least one parameter to match on.")
+
+        self.expected_exceptions = tuple(
+            self._parse_exc(e, expected="a BaseException type")
+            for e in expected_exceptions
+        )
+
+        self._just_propagate = False
+
+    def matches(
+        self,
+        exception: BaseException | None,
+    ) -> TypeGuard[BaseExcT_co_default]:
+        """Check if an exception matches the requirements of this :class:`RaisesExc`.
+        If it fails, :attr:`RaisesExc.fail_reason` will be set.
+
+        Examples::
+
+            assert RaisesExc(ValueError).matches(my_exception):
+            # is equivalent to
+            assert isinstance(my_exception, ValueError)
+
+            # this can be useful when checking e.g. the ``__cause__`` of an exception.
+            with pytest.raises(ValueError) as excinfo:
+                ...
+            assert RaisesExc(SyntaxError, match="foo").matches(excinfo.value.__cause__)
+            # above line is equivalent to
+            assert isinstance(excinfo.value.__cause__, SyntaxError)
+            assert re.search("foo", str(excinfo.value.__cause__)
+
+        """
+        self._just_propagate = False
+        if exception is None:
+            self._fail_reason = "exception is None"
+            return False
+        if not self._check_type(exception):
+            self._just_propagate = True
+            return False
+
+        if not self._check_match(exception):
+            return False
+
+        return self._check_check(exception)
+
+    def __repr__(self) -> str:
+        parameters = []
+        if self.expected_exceptions:
+            parameters.append(_exception_type_name(self.expected_exceptions))
+        if self.match is not None:
+            # If no flags were specified, discard the redundant re.compile() here.
+            parameters.append(
+                f"match={_match_pattern(self.match)!r}",
+            )
+        if self.check is not None:
+            parameters.append(f"check={repr_callable(self.check)}")
+        return f"RaisesExc({', '.join(parameters)})"
+
+    def _check_type(self, exception: BaseException) -> TypeGuard[BaseExcT_co_default]:
+        self._fail_reason = _check_raw_type(self.expected_exceptions, exception)
+        return self._fail_reason is None
+
+    def __enter__(self) -> ExceptionInfo[BaseExcT_co_default]:
+        self.excinfo: ExceptionInfo[BaseExcT_co_default] = ExceptionInfo.for_later()
+        return self.excinfo
+
+    # TODO: move common code into superclass
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: types.TracebackType | None,
+    ) -> bool:
+        __tracebackhide__ = True
+        if exc_type is None:
+            if not self.expected_exceptions:
+                fail("DID NOT RAISE any exception")
+            if len(self.expected_exceptions) > 1:
+                fail(f"DID NOT RAISE any of {self.expected_exceptions!r}")
+
+            fail(f"DID NOT RAISE {self.expected_exceptions[0]!r}")
+
+        assert self.excinfo is not None, (
+            "Internal error - should have been constructed in __enter__"
+        )
+
+        if not self.matches(exc_val):
+            if self._just_propagate:
+                return False
+            raise AssertionError(self._fail_reason)
+
+        # Cast to narrow the exception type now that it's verified....
+        # even though the TypeGuard in self.matches should be narrowing
+        exc_info = cast(
+            "tuple[type[BaseExcT_co_default], BaseExcT_co_default, types.TracebackType]",
+            (exc_type, exc_val, exc_tb),
+        )
+        self.excinfo.fill_unfilled(exc_info)
+        return True
+
+
+@final
+class RaisesGroup(AbstractRaises[BaseExceptionGroup[BaseExcT_co]]):
+    """
+    .. versionadded:: 8.4
+
+    Contextmanager for checking for an expected :exc:`ExceptionGroup`.
+    This works similar to :func:`pytest.raises`, but allows for specifying the structure of an :exc:`ExceptionGroup`.
+    :meth:`ExceptionInfo.group_contains` also tries to handle exception groups,
+    but it is very bad at checking that you *didn't* get unexpected exceptions.
+
+    The catching behaviour differs from :ref:`except* <except_star>`, being much
+    stricter about the structure by default.
+    By using ``allow_unwrapped=True`` and ``flatten_subgroups=True`` you can match
+    :ref:`except* <except_star>` fully when expecting a single exception.
+
+    :param args:
+        Any number of exception types, :class:`RaisesGroup` or :class:`RaisesExc`
+        to specify the exceptions contained in this exception.
+        All specified exceptions must be present in the raised group, *and no others*.
+
+        If you expect a variable number of exceptions you need to use
+        :func:`pytest.raises(ExceptionGroup) <pytest.raises>` and manually check
+        the contained exceptions. Consider making use of :meth:`RaisesExc.matches`.
+
+        It does not care about the order of the exceptions, so
+        ``RaisesGroup(ValueError, TypeError)``
+        is equivalent to
+        ``RaisesGroup(TypeError, ValueError)``.
+    :kwparam str | re.Pattern[str] | None match:
+        If specified, a string containing a regular expression,
+        or a regular expression object, that is tested against the string
+        representation of the exception group and its :pep:`678` `__notes__`
+        using :func:`re.search`.
+
+        To match a literal string that may contain :ref:`special characters
+        <re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
+
+        Note that " (5 subgroups)" will be stripped from the ``repr`` before matching.
+    :kwparam Callable[[E], bool] check:
+        If specified, a callable that will be called with the group as a parameter
+        after successfully matching the expected exceptions. If it returns ``True``
+        it will be considered a match, if not it will be considered a failed match.
+    :kwparam bool allow_unwrapped:
+        If expecting a single exception or :class:`RaisesExc` it will match even
+        if the exception is not inside an exceptiongroup.
+
+        Using this together with ``match``, ``check`` or expecting multiple exceptions
+        will raise an error.
+    :kwparam bool flatten_subgroups:
+        "flatten" any groups inside the raised exception group, extracting all exceptions
+        inside any nested groups, before matching. Without this it expects you to
+        fully specify the nesting structure by passing :class:`RaisesGroup` as expected
+        parameter.
+
+    Examples::
+
+        with RaisesGroup(ValueError):
+            raise ExceptionGroup("", (ValueError(),))
+        # match
+        with RaisesGroup(
+            ValueError,
+            ValueError,
+            RaisesExc(TypeError, match="^expected int$"),
+            match="^my group$",
+        ):
+            raise ExceptionGroup(
+                "my group",
+                [
+                    ValueError(),
+                    TypeError("expected int"),
+                    ValueError(),
+                ],
+            )
+        # check
+        with RaisesGroup(
+            KeyboardInterrupt,
+            match="^hello$",
+            check=lambda x: isinstance(x.__cause__, ValueError),
+        ):
+            raise BaseExceptionGroup("hello", [KeyboardInterrupt()]) from ValueError
+        # nested groups
+        with RaisesGroup(RaisesGroup(ValueError)):
+            raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
+
+        # flatten_subgroups
+        with RaisesGroup(ValueError, flatten_subgroups=True):
+            raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
+
+        # allow_unwrapped
+        with RaisesGroup(ValueError, allow_unwrapped=True):
+            raise ValueError
+
+
+    :meth:`RaisesGroup.matches` can also be used directly to check a standalone exception group.
+
+
+    The matching algorithm is greedy, which means cases such as this may fail::
+
+        with RaisesGroup(ValueError, RaisesExc(ValueError, match="hello")):
+            raise ExceptionGroup("", (ValueError("hello"), ValueError("goodbye")))
+
+    even though it generally does not care about the order of the exceptions in the group.
+    To avoid the above you should specify the first :exc:`ValueError` with a :class:`RaisesExc` as well.
+
+    .. note::
+        When raised exceptions don't match the expected ones, you'll get a detailed error
+        message explaining why. This includes ``repr(check)`` if set, which in Python can be
+        overly verbose, showing memory locations etc etc.
+
+        If installed and imported (in e.g. ``conftest.py``), the ``hypothesis`` library will
+        monkeypatch this output to provide shorter & more readable repr's.
+    """
+
+    # allow_unwrapped=True requires: singular exception, exception not being
+    # RaisesGroup instance, match is None, check is None
+    @overload
+    def __init__(
+        self,
+        expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co],
+        /,
+        *,
+        allow_unwrapped: Literal[True],
+        flatten_subgroups: bool = False,
+    ) -> None: ...
+
+    # flatten_subgroups = True also requires no nested RaisesGroup
+    @overload
+    def __init__(
+        self,
+        expected_exception: type[BaseExcT_co] | RaisesExc[BaseExcT_co],
+        /,
+        *other_exceptions: type[BaseExcT_co] | RaisesExc[BaseExcT_co],
+        flatten_subgroups: Literal[True],
+        match: str | Pattern[str] | None = None,
+        check: Callable[[BaseExceptionGroup[BaseExcT_co]], bool] | None = None,
+    ) -> None: ...
+
+    # simplify the typevars if possible (the following 3 are equivalent but go simpler->complicated)
+    # ... the first handles RaisesGroup[ValueError], the second RaisesGroup[ExceptionGroup[ValueError]],
+    #     the third RaisesGroup[ValueError | ExceptionGroup[ValueError]].
+    # ... otherwise, we will get results like RaisesGroup[ValueError | ExceptionGroup[Never]] (I think)
+    #     (technically correct but misleading)
+    @overload
+    def __init__(
+        self: RaisesGroup[ExcT_1],
+        expected_exception: type[ExcT_1] | RaisesExc[ExcT_1],
+        /,
+        *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1],
+        match: str | Pattern[str] | None = None,
+        check: Callable[[ExceptionGroup[ExcT_1]], bool] | None = None,
+    ) -> None: ...
+
+    @overload
+    def __init__(
+        self: RaisesGroup[ExceptionGroup[ExcT_2]],
+        expected_exception: RaisesGroup[ExcT_2],
+        /,
+        *other_exceptions: RaisesGroup[ExcT_2],
+        match: str | Pattern[str] | None = None,
+        check: Callable[[ExceptionGroup[ExceptionGroup[ExcT_2]]], bool] | None = None,
+    ) -> None: ...
+
+    @overload
+    def __init__(
+        self: RaisesGroup[ExcT_1 | ExceptionGroup[ExcT_2]],
+        expected_exception: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2],
+        /,
+        *other_exceptions: type[ExcT_1] | RaisesExc[ExcT_1] | RaisesGroup[ExcT_2],
+        match: str | Pattern[str] | None = None,
+        check: (
+            Callable[[ExceptionGroup[ExcT_1 | ExceptionGroup[ExcT_2]]], bool] | None
+        ) = None,
+    ) -> None: ...
+
+    # same as the above 3 but handling BaseException
+    @overload
+    def __init__(
+        self: RaisesGroup[BaseExcT_1],
+        expected_exception: type[BaseExcT_1] | RaisesExc[BaseExcT_1],
+        /,
+        *other_exceptions: type[BaseExcT_1] | RaisesExc[BaseExcT_1],
+        match: str | Pattern[str] | None = None,
+        check: Callable[[BaseExceptionGroup[BaseExcT_1]], bool] | None = None,
+    ) -> None: ...
+
+    @overload
+    def __init__(
+        self: RaisesGroup[BaseExceptionGroup[BaseExcT_2]],
+        expected_exception: RaisesGroup[BaseExcT_2],
+        /,
+        *other_exceptions: RaisesGroup[BaseExcT_2],
+        match: str | Pattern[str] | None = None,
+        check: (
+            Callable[[BaseExceptionGroup[BaseExceptionGroup[BaseExcT_2]]], bool] | None
+        ) = None,
+    ) -> None: ...
+
+    @overload
+    def __init__(
+        self: RaisesGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]],
+        expected_exception: type[BaseExcT_1]
+        | RaisesExc[BaseExcT_1]
+        | RaisesGroup[BaseExcT_2],
+        /,
+        *other_exceptions: type[BaseExcT_1]
+        | RaisesExc[BaseExcT_1]
+        | RaisesGroup[BaseExcT_2],
+        match: str | Pattern[str] | None = None,
+        check: (
+            Callable[
+                [BaseExceptionGroup[BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]]],
+                bool,
+            ]
+            | None
+        ) = None,
+    ) -> None: ...
+
+    def __init__(
+        self: RaisesGroup[ExcT_1 | BaseExcT_1 | BaseExceptionGroup[BaseExcT_2]],
+        expected_exception: type[BaseExcT_1]
+        | RaisesExc[BaseExcT_1]
+        | RaisesGroup[BaseExcT_2],
+        /,
+        *other_exceptions: type[BaseExcT_1]
+        | RaisesExc[BaseExcT_1]
+        | RaisesGroup[BaseExcT_2],
+        allow_unwrapped: bool = False,
+        flatten_subgroups: bool = False,
+        match: str | Pattern[str] | None = None,
+        check: (
+            Callable[[BaseExceptionGroup[BaseExcT_1]], bool]
+            | Callable[[ExceptionGroup[ExcT_1]], bool]
+            | None
+        ) = None,
+    ):
+        # The type hint on the `self` and `check` parameters uses different formats
+        # that are *very* hard to reconcile while adhering to the overloads, so we cast
+        # it to avoid an error when passing it to super().__init__
+        check = cast(
+            "Callable[["
+            "BaseExceptionGroup[ExcT_1|BaseExcT_1|BaseExceptionGroup[BaseExcT_2]]"
+            "], bool]",
+            check,
+        )
+        super().__init__(match=match, check=check)
+        self.allow_unwrapped = allow_unwrapped
+        self.flatten_subgroups: bool = flatten_subgroups
+        self.is_baseexception = False
+
+        if allow_unwrapped and other_exceptions:
+            raise ValueError(
+                "You cannot specify multiple exceptions with `allow_unwrapped=True.`"
+                " If you want to match one of multiple possible exceptions you should"
+                " use a `RaisesExc`."
+                " E.g. `RaisesExc(check=lambda e: isinstance(e, (...)))`",
+            )
+        if allow_unwrapped and isinstance(expected_exception, RaisesGroup):
+            raise ValueError(
+                "`allow_unwrapped=True` has no effect when expecting a `RaisesGroup`."
+                " You might want it in the expected `RaisesGroup`, or"
+                " `flatten_subgroups=True` if you don't care about the structure.",
+            )
+        if allow_unwrapped and (match is not None or check is not None):
+            raise ValueError(
+                "`allow_unwrapped=True` bypasses the `match` and `check` parameters"
+                " if the exception is unwrapped. If you intended to match/check the"
+                " exception you should use a `RaisesExc` object. If you want to match/check"
+                " the exceptiongroup when the exception *is* wrapped you need to"
+                " do e.g. `if isinstance(exc.value, ExceptionGroup):"
+                " assert RaisesGroup(...).matches(exc.value)` afterwards.",
+            )
+
+        self.expected_exceptions: tuple[
+            type[BaseExcT_co] | RaisesExc[BaseExcT_co] | RaisesGroup[BaseException], ...
+        ] = tuple(
+            self._parse_excgroup(e, "a BaseException type, RaisesExc, or RaisesGroup")
+            for e in (
+                expected_exception,
+                *other_exceptions,
+            )
+        )
+
+    def _parse_excgroup(
+        self,
+        exc: (
+            type[BaseExcT_co]
+            | types.GenericAlias
+            | RaisesExc[BaseExcT_1]
+            | RaisesGroup[BaseExcT_2]
+        ),
+        expected: str,
+    ) -> type[BaseExcT_co] | RaisesExc[BaseExcT_1] | RaisesGroup[BaseExcT_2]:
+        # verify exception type and set `self.is_baseexception`
+        if isinstance(exc, RaisesGroup):
+            if self.flatten_subgroups:
+                raise ValueError(
+                    "You cannot specify a nested structure inside a RaisesGroup with"
+                    " `flatten_subgroups=True`. The parameter will flatten subgroups"
+                    " in the raised exceptiongroup before matching, which would never"
+                    " match a nested structure.",
+                )
+            self.is_baseexception |= exc.is_baseexception
+            exc._nested = True
+            return exc
+        elif isinstance(exc, RaisesExc):
+            self.is_baseexception |= exc.is_baseexception
+            exc._nested = True
+            return exc
+        elif isinstance(exc, tuple):
+            raise TypeError(
+                f"expected exception must be {expected}, not {type(exc).__name__!r}.\n"
+                "RaisesGroup does not support tuples of exception types when expecting one of "
+                "several possible exception types like RaisesExc.\n"
+                "If you meant to expect a group with multiple exceptions, list them as separate arguments."
+            )
+        else:
+            return super()._parse_exc(exc, expected)
+
+    @overload
+    def __enter__(
+        self: RaisesGroup[ExcT_1],
+    ) -> ExceptionInfo[ExceptionGroup[ExcT_1]]: ...
+    @overload
+    def __enter__(
+        self: RaisesGroup[BaseExcT_1],
+    ) -> ExceptionInfo[BaseExceptionGroup[BaseExcT_1]]: ...
+
+    def __enter__(self) -> ExceptionInfo[BaseExceptionGroup[BaseException]]:
+        self.excinfo: ExceptionInfo[BaseExceptionGroup[BaseExcT_co]] = (
+            ExceptionInfo.for_later()
+        )
+        return self.excinfo
+
+    def __repr__(self) -> str:
+        reqs = [
+            e.__name__ if isinstance(e, type) else repr(e)
+            for e in self.expected_exceptions
+        ]
+        if self.allow_unwrapped:
+            reqs.append(f"allow_unwrapped={self.allow_unwrapped}")
+        if self.flatten_subgroups:
+            reqs.append(f"flatten_subgroups={self.flatten_subgroups}")
+        if self.match is not None:
+            # If no flags were specified, discard the redundant re.compile() here.
+            reqs.append(f"match={_match_pattern(self.match)!r}")
+        if self.check is not None:
+            reqs.append(f"check={repr_callable(self.check)}")
+        return f"RaisesGroup({', '.join(reqs)})"
+
+    def _unroll_exceptions(
+        self,
+        exceptions: Sequence[BaseException],
+    ) -> Sequence[BaseException]:
+        """Used if `flatten_subgroups=True`."""
+        res: list[BaseException] = []
+        for exc in exceptions:
+            if isinstance(exc, BaseExceptionGroup):
+                res.extend(self._unroll_exceptions(exc.exceptions))
+
+            else:
+                res.append(exc)
+        return res
+
+    @overload
+    def matches(
+        self: RaisesGroup[ExcT_1],
+        exception: BaseException | None,
+    ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ...
+    @overload
+    def matches(
+        self: RaisesGroup[BaseExcT_1],
+        exception: BaseException | None,
+    ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ...
+
+    def matches(
+        self,
+        exception: BaseException | None,
+    ) -> TypeGuard[BaseExceptionGroup[BaseExcT_co]]:
+        """Check if an exception matches the requirements of this RaisesGroup.
+        If it fails, `RaisesGroup.fail_reason` will be set.
+
+        Example::
+
+            with pytest.raises(TypeError) as excinfo:
+                ...
+            assert RaisesGroup(ValueError).matches(excinfo.value.__cause__)
+            # the above line is equivalent to
+            myexc = excinfo.value.__cause
+            assert isinstance(myexc, BaseExceptionGroup)
+            assert len(myexc.exceptions) == 1
+            assert isinstance(myexc.exceptions[0], ValueError)
+        """
+        self._fail_reason = None
+        if exception is None:
+            self._fail_reason = "exception is None"
+            return False
+        if not isinstance(exception, BaseExceptionGroup):
+            # we opt to only print type of the exception here, as the repr would
+            # likely be quite long
+            not_group_msg = f"`{type(exception).__name__}()` is not an exception group"
+            if len(self.expected_exceptions) > 1:
+                self._fail_reason = not_group_msg
+                return False
+            # if we have 1 expected exception, check if it would work even if
+            # allow_unwrapped is not set
+            res = self._check_expected(self.expected_exceptions[0], exception)
+            if res is None and self.allow_unwrapped:
+                return True
+
+            if res is None:
+                self._fail_reason = (
+                    f"{not_group_msg}, but would match with `allow_unwrapped=True`"
+                )
+            elif self.allow_unwrapped:
+                self._fail_reason = res
+            else:
+                self._fail_reason = not_group_msg
+            return False
+
+        actual_exceptions: Sequence[BaseException] = exception.exceptions
+        if self.flatten_subgroups:
+            actual_exceptions = self._unroll_exceptions(actual_exceptions)
+
+        if not self._check_match(exception):
+            self._fail_reason = cast(str, self._fail_reason)
+            old_reason = self._fail_reason
+            if (
+                len(actual_exceptions) == len(self.expected_exceptions) == 1
+                and isinstance(expected := self.expected_exceptions[0], type)
+                and isinstance(actual := actual_exceptions[0], expected)
+                and self._check_match(actual)
+            ):
+                assert self.match is not None, "can't be None if _check_match failed"
+                assert self._fail_reason is old_reason is not None
+                self._fail_reason += (
+                    f"\n"
+                    f" but matched the expected `{self._repr_expected(expected)}`.\n"
+                    f" You might want "
+                    f"`RaisesGroup(RaisesExc({expected.__name__}, match={_match_pattern(self.match)!r}))`"
+                )
+            else:
+                self._fail_reason = old_reason
+            return False
+
+        # do the full check on expected exceptions
+        if not self._check_exceptions(
+            exception,
+            actual_exceptions,
+        ):
+            self._fail_reason = cast(str, self._fail_reason)
+            assert self._fail_reason is not None
+            old_reason = self._fail_reason
+            # if we're not expecting a nested structure, and there is one, do a second
+            # pass where we try flattening it
+            if (
+                not self.flatten_subgroups
+                and not any(
+                    isinstance(e, RaisesGroup) for e in self.expected_exceptions
+                )
+                and any(isinstance(e, BaseExceptionGroup) for e in actual_exceptions)
+                and self._check_exceptions(
+                    exception,
+                    self._unroll_exceptions(exception.exceptions),
+                )
+            ):
+                # only indent if it's a single-line reason. In a multi-line there's already
+                # indented lines that this does not belong to.
+                indent = "  " if "\n" not in self._fail_reason else ""
+                self._fail_reason = (
+                    old_reason
+                    + f"\n{indent}Did you mean to use `flatten_subgroups=True`?"
+                )
+            else:
+                self._fail_reason = old_reason
+            return False
+
+        # Only run `self.check` once we know `exception` is of the correct type.
+        if not self._check_check(exception):
+            reason = (
+                cast(str, self._fail_reason) + f" on the {type(exception).__name__}"
+            )
+            if (
+                len(actual_exceptions) == len(self.expected_exceptions) == 1
+                and isinstance(expected := self.expected_exceptions[0], type)
+                # we explicitly break typing here :)
+                and self._check_check(actual_exceptions[0])  # type: ignore[arg-type]
+            ):
+                self._fail_reason = reason + (
+                    f", but did return True for the expected {self._repr_expected(expected)}."
+                    f" You might want RaisesGroup(RaisesExc({expected.__name__}, check=<...>))"
+                )
+            else:
+                self._fail_reason = reason
+            return False
+
+        return True
+
+    @staticmethod
+    def _check_expected(
+        expected_type: (
+            type[BaseException] | RaisesExc[BaseException] | RaisesGroup[BaseException]
+        ),
+        exception: BaseException,
+    ) -> str | None:
+        """Helper method for `RaisesGroup.matches` and `RaisesGroup._check_exceptions`
+        to check one of potentially several expected exceptions."""
+        if isinstance(expected_type, type):
+            return _check_raw_type(expected_type, exception)
+        res = expected_type.matches(exception)
+        if res:
+            return None
+        assert expected_type.fail_reason is not None
+        if expected_type.fail_reason.startswith("\n"):
+            return f"\n{expected_type!r}: {indent(expected_type.fail_reason, '  ')}"
+        return f"{expected_type!r}: {expected_type.fail_reason}"
+
+    @staticmethod
+    def _repr_expected(e: type[BaseException] | AbstractRaises[BaseException]) -> str:
+        """Get the repr of an expected type/RaisesExc/RaisesGroup, but we only want
+        the name if it's a type"""
+        if isinstance(e, type):
+            return _exception_type_name(e)
+        return repr(e)
+
+    @overload
+    def _check_exceptions(
+        self: RaisesGroup[ExcT_1],
+        _exception: Exception,
+        actual_exceptions: Sequence[Exception],
+    ) -> TypeGuard[ExceptionGroup[ExcT_1]]: ...
+    @overload
+    def _check_exceptions(
+        self: RaisesGroup[BaseExcT_1],
+        _exception: BaseException,
+        actual_exceptions: Sequence[BaseException],
+    ) -> TypeGuard[BaseExceptionGroup[BaseExcT_1]]: ...
+
+    def _check_exceptions(
+        self,
+        _exception: BaseException,
+        actual_exceptions: Sequence[BaseException],
+    ) -> TypeGuard[BaseExceptionGroup[BaseExcT_co]]:
+        """Helper method for RaisesGroup.matches that attempts to pair up expected and actual exceptions"""
+        # The _exception parameter is not used, but necessary for the TypeGuard
+
+        # full table with all results
+        results = ResultHolder(self.expected_exceptions, actual_exceptions)
+
+        # (indexes of) raised exceptions that haven't (yet) found an expected
+        remaining_actual = list(range(len(actual_exceptions)))
+        # (indexes of) expected exceptions that haven't found a matching raised
+        failed_expected: list[int] = []
+        # successful greedy matches
+        matches: dict[int, int] = {}
+
+        # loop over expected exceptions first to get a more predictable result
+        for i_exp, expected in enumerate(self.expected_exceptions):
+            for i_rem in remaining_actual:
+                res = self._check_expected(expected, actual_exceptions[i_rem])
+                results.set_result(i_exp, i_rem, res)
+                if res is None:
+                    remaining_actual.remove(i_rem)
+                    matches[i_exp] = i_rem
+                    break
+            else:
+                failed_expected.append(i_exp)
+
+        # All exceptions matched up successfully
+        if not remaining_actual and not failed_expected:
+            return True
+
+        # in case of a single expected and single raised we simplify the output
+        if 1 == len(actual_exceptions) == len(self.expected_exceptions):
+            assert not matches
+            self._fail_reason = res
+            return False
+
+        # The test case is failing, so we can do a slow and exhaustive check to find
+        # duplicate matches etc that will be helpful in debugging
+        for i_exp, expected in enumerate(self.expected_exceptions):
+            for i_actual, actual in enumerate(actual_exceptions):
+                if results.has_result(i_exp, i_actual):
+                    continue
+                results.set_result(
+                    i_exp, i_actual, self._check_expected(expected, actual)
+                )
+
+        successful_str = (
+            f"{len(matches)} matched exception{'s' if len(matches) > 1 else ''}. "
+            if matches
+            else ""
+        )
+
+        # all expected were found
+        if not failed_expected and results.no_match_for_actual(remaining_actual):
+            self._fail_reason = (
+                f"{successful_str}Unexpected exception(s):"
+                f" {[actual_exceptions[i] for i in remaining_actual]!r}"
+            )
+            return False
+        # all raised exceptions were expected
+        if not remaining_actual and results.no_match_for_expected(failed_expected):
+            no_match_for_str = ", ".join(
+                self._repr_expected(self.expected_exceptions[i])
+                for i in failed_expected
+            )
+            self._fail_reason = f"{successful_str}Too few exceptions raised, found no match for: [{no_match_for_str}]"
+            return False
+
+        # if there's only one remaining and one failed, and the unmatched didn't match anything else,
+        # we elect to only print why the remaining and the failed didn't match.
+        if (
+            1 == len(remaining_actual) == len(failed_expected)
+            and results.no_match_for_actual(remaining_actual)
+            and results.no_match_for_expected(failed_expected)
+        ):
+            self._fail_reason = f"{successful_str}{results.get_result(failed_expected[0], remaining_actual[0])}"
+            return False
+
+        # there's both expected and raised exceptions without matches
+        s = ""
+        if matches:
+            s += f"\n{successful_str}"
+        indent_1 = " " * 2
+        indent_2 = " " * 4
+
+        if not remaining_actual:
+            s += "\nToo few exceptions raised!"
+        elif not failed_expected:
+            s += "\nUnexpected exception(s)!"
+
+        if failed_expected:
+            s += "\nThe following expected exceptions did not find a match:"
+            rev_matches = {v: k for k, v in matches.items()}
+        for i_failed in failed_expected:
+            s += (
+                f"\n{indent_1}{self._repr_expected(self.expected_exceptions[i_failed])}"
+            )
+            for i_actual, actual in enumerate(actual_exceptions):
+                if results.get_result(i_exp, i_actual) is None:
+                    # we print full repr of match target
+                    s += (
+                        f"\n{indent_2}It matches {backquote(repr(actual))} which was paired with "
+                        + backquote(
+                            self._repr_expected(
+                                self.expected_exceptions[rev_matches[i_actual]]
+                            )
+                        )
+                    )
+
+        if remaining_actual:
+            s += "\nThe following raised exceptions did not find a match"
+        for i_actual in remaining_actual:
+            s += f"\n{indent_1}{actual_exceptions[i_actual]!r}:"
+            for i_exp, expected in enumerate(self.expected_exceptions):
+                res = results.get_result(i_exp, i_actual)
+                if i_exp in failed_expected:
+                    assert res is not None
+                    if res[0] != "\n":
+                        s += "\n"
+                    s += indent(res, indent_2)
+                if res is None:
+                    # we print full repr of match target
+                    s += (
+                        f"\n{indent_2}It matches {backquote(self._repr_expected(expected))} "
+                        f"which was paired with {backquote(repr(actual_exceptions[matches[i_exp]]))}"
+                    )
+
+        if len(self.expected_exceptions) == len(actual_exceptions) and possible_match(
+            results
+        ):
+            s += (
+                "\nThere exist a possible match when attempting an exhaustive check,"
+                " but RaisesGroup uses a greedy algorithm. "
+                "Please make your expected exceptions more stringent with `RaisesExc` etc"
+                " so the greedy algorithm can function."
+            )
+        self._fail_reason = s
+        return False
+
+    def __exit__(
+        self,
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: types.TracebackType | None,
+    ) -> bool:
+        __tracebackhide__ = True
+        if exc_type is None:
+            fail(f"DID NOT RAISE any exception, expected `{self.expected_type()}`")
+
+        assert self.excinfo is not None, (
+            "Internal error - should have been constructed in __enter__"
+        )
+
+        # group_str is the only thing that differs between RaisesExc and RaisesGroup...
+        # I might just scrap it? Or make it part of fail_reason
+        group_str = (
+            "(group)"
+            if self.allow_unwrapped and not issubclass(exc_type, BaseExceptionGroup)
+            else "group"
+        )
+
+        if not self.matches(exc_val):
+            fail(f"Raised exception {group_str} did not match: {self._fail_reason}")
+
+        # Cast to narrow the exception type now that it's verified....
+        # even though the TypeGuard in self.matches should be narrowing
+        exc_info = cast(
+            "tuple[type[BaseExceptionGroup[BaseExcT_co]], BaseExceptionGroup[BaseExcT_co], types.TracebackType]",
+            (exc_type, exc_val, exc_tb),
+        )
+        self.excinfo.fill_unfilled(exc_info)
+        return True
+
+    def expected_type(self) -> str:
+        subexcs = []
+        for e in self.expected_exceptions:
+            if isinstance(e, RaisesExc):
+                subexcs.append(repr(e))
+            elif isinstance(e, RaisesGroup):
+                subexcs.append(e.expected_type())
+            elif isinstance(e, type):
+                subexcs.append(e.__name__)
+            else:  # pragma: no cover
+                raise AssertionError("unknown type")
+        group_type = "Base" if self.is_baseexception else ""
+        return f"{group_type}ExceptionGroup({', '.join(subexcs)})"
+
+
+@final
+class NotChecked:
+    """Singleton for unchecked values in ResultHolder"""
+
+
+class ResultHolder:
+    """Container for results of checking exceptions.
+    Used in RaisesGroup._check_exceptions and possible_match.
+    """
+
+    def __init__(
+        self,
+        expected_exceptions: tuple[
+            type[BaseException] | AbstractRaises[BaseException], ...
+        ],
+        actual_exceptions: Sequence[BaseException],
+    ) -> None:
+        self.results: list[list[str | type[NotChecked] | None]] = [
+            [NotChecked for _ in expected_exceptions] for _ in actual_exceptions
+        ]
+
+    def set_result(self, expected: int, actual: int, result: str | None) -> None:
+        self.results[actual][expected] = result
+
+    def get_result(self, expected: int, actual: int) -> str | None:
+        res = self.results[actual][expected]
+        assert res is not NotChecked
+        # mypy doesn't support identity checking against anything but None
+        return res  # type: ignore[return-value]
+
+    def has_result(self, expected: int, actual: int) -> bool:
+        return self.results[actual][expected] is not NotChecked
+
+    def no_match_for_expected(self, expected: list[int]) -> bool:
+        for i in expected:
+            for actual_results in self.results:
+                assert actual_results[i] is not NotChecked
+                if actual_results[i] is None:
+                    return False
+        return True
+
+    def no_match_for_actual(self, actual: list[int]) -> bool:
+        for i in actual:
+            for res in self.results[i]:
+                assert res is not NotChecked
+                if res is None:
+                    return False
+        return True
+
+
+def possible_match(results: ResultHolder, used: set[int] | None = None) -> bool:
+    if used is None:
+        used = set()
+    curr_row = len(used)
+    if curr_row == len(results.results):
+        return True
+    return any(
+        val is None and i not in used and possible_match(results, used | {i})
+        for (i, val) in enumerate(results.results[curr_row])
+    )
diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py
index 175b571a80c..440e3efac8a 100644
--- a/src/_pytest/recwarn.py
+++ b/src/_pytest/recwarn.py
@@ -1,24 +1,29 @@
+# mypy: allow-untyped-defs
 """Record warnings during test function execution."""
+
+from __future__ import annotations
+
+from collections.abc import Callable
+from collections.abc import Generator
+from collections.abc import Iterator
+from pprint import pformat
 import re
-import warnings
 from types import TracebackType
 from typing import Any
-from typing import Callable
-from typing import Generator
-from typing import Iterator
-from typing import List
-from typing import Optional
+from typing import final
 from typing import overload
-from typing import Pattern
-from typing import Tuple
-from typing import Type
+from typing import TYPE_CHECKING
 from typing import TypeVar
-from typing import Union
 
-from _pytest.compat import final
+
+if TYPE_CHECKING:
+    from typing_extensions import Self
+
+import warnings
+
 from _pytest.deprecated import check_ispytest
-from _pytest.deprecated import WARNS_NONE_ARG
 from _pytest.fixtures import fixture
+from _pytest.outcomes import Exit
 from _pytest.outcomes import fail
 
 
@@ -26,11 +31,10 @@
 
 
 @fixture
-def recwarn() -> Generator["WarningsRecorder", None, None]:
+def recwarn() -> Generator[WarningsRecorder]:
     """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
 
-    See https://docs.python.org/library/how-to/capture-warnings.html for information
-    on warning categories.
+    See :ref:`warnings` for information on warning categories.
     """
     wrec = WarningsRecorder(_ispytest=True)
     with wrec:
@@ -40,20 +44,18 @@ def recwarn() -> Generator["WarningsRecorder", None, None]:
 
 @overload
 def deprecated_call(
-    *, match: Optional[Union[str, Pattern[str]]] = ...
-) -> "WarningsRecorder":
-    ...
+    *, match: str | re.Pattern[str] | None = ...
+) -> WarningsRecorder: ...
 
 
 @overload
-def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
-    ...
+def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ...
 
 
 def deprecated_call(
-    func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any
-) -> Union["WarningsRecorder", Any]:
-    """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning``.
+    func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any
+) -> WarningsRecorder | Any:
+    """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``.
 
     This function can be used as a context manager::
 
@@ -78,46 +80,46 @@ def deprecated_call(
     """
     __tracebackhide__ = True
     if func is not None:
-        args = (func,) + args
-    return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs)
+        args = (func, *args)
+    return warns(
+        (DeprecationWarning, PendingDeprecationWarning, FutureWarning), *args, **kwargs
+    )
 
 
 @overload
 def warns(
-    expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = ...,
+    expected_warning: type[Warning] | tuple[type[Warning], ...] = ...,
     *,
-    match: Optional[Union[str, Pattern[str]]] = ...,
-) -> "WarningsChecker":
-    ...
+    match: str | re.Pattern[str] | None = ...,
+) -> WarningsChecker: ...
 
 
 @overload
 def warns(
-    expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]],
+    expected_warning: type[Warning] | tuple[type[Warning], ...],
     func: Callable[..., T],
     *args: Any,
     **kwargs: Any,
-) -> T:
-    ...
+) -> T: ...
 
 
 def warns(
-    expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning,
+    expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
     *args: Any,
-    match: Optional[Union[str, Pattern[str]]] = None,
+    match: str | re.Pattern[str] | None = None,
     **kwargs: Any,
-) -> Union["WarningsChecker", Any]:
+) -> WarningsChecker | Any:
     r"""Assert that code raises a particular class of warning.
 
-    Specifically, the parameter ``expected_warning`` can be a warning class or
-    sequence of warning classes, and the inside the ``with`` block must issue a warning of that class or
-    classes.
+    Specifically, the parameter ``expected_warning`` can be a warning class or tuple
+    of warning classes, and the code inside the ``with`` block must issue at least one
+    warning of that class or classes.
 
-    This helper produces a list of :class:`warnings.WarningMessage` objects,
-    one for each warning raised.
+    This helper produces a list of :class:`warnings.WarningMessage` objects, one for
+    each warning emitted (regardless of whether it is an ``expected_warning`` or not).
+    Since pytest 8.0, unmatched warnings are also re-emitted when the context closes.
 
-    This function can be used as a context manager, or any of the other ways
-    :func:`pytest.raises` can be used::
+    This function can be used as a context manager::
 
         >>> import pytest
         >>> with pytest.warns(RuntimeWarning):
@@ -132,20 +134,30 @@ def warns(
         >>> with pytest.warns(UserWarning, match=r'must be \d+$'):
         ...     warnings.warn("value must be 42", UserWarning)
 
-        >>> with pytest.warns(UserWarning, match=r'must be \d+$'):
-        ...     warnings.warn("this is not here", UserWarning)
+        >>> with pytest.warns(UserWarning):  # catch re-emitted warning
+        ...     with pytest.warns(UserWarning, match=r'must be \d+$'):
+        ...         warnings.warn("this is not here", UserWarning)
         Traceback (most recent call last):
           ...
         Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
 
+    **Using with** ``pytest.mark.parametrize``
+
+    When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests
+    such that some runs raise a warning and others do not.
+
+    This could be achieved in the same way as with exceptions, see
+    :ref:`parametrizing_conditional_raising` for an example.
+
     """
     __tracebackhide__ = True
     if not args:
         if kwargs:
-            msg = "Unexpected keyword arguments passed to pytest.warns: "
-            msg += ", ".join(sorted(kwargs))
-            msg += "\nUse context-manager form instead?"
-            raise TypeError(msg)
+            argnames = ", ".join(sorted(kwargs))
+            raise TypeError(
+                f"Unexpected keyword arguments passed to pytest.warns: {argnames}"
+                "\nUse context-manager form instead?"
+            )
         return WarningsChecker(expected_warning, match_expr=match, _ispytest=True)
     else:
         func = args[0]
@@ -155,29 +167,35 @@ def warns(
             return func(*args[1:], **kwargs)
 
 
-class WarningsRecorder(warnings.catch_warnings):
+class WarningsRecorder(warnings.catch_warnings):  # type:ignore[type-arg]
     """A context manager to record raised warnings.
 
+    Each recorded warning is an instance of :class:`warnings.WarningMessage`.
+
     Adapted from `warnings.catch_warnings`.
+
+    .. note::
+        ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
+        differently; see :ref:`ensuring_function_triggers`.
+
     """
 
     def __init__(self, *, _ispytest: bool = False) -> None:
         check_ispytest(_ispytest)
-        # Type ignored due to the way typeshed handles warnings.catch_warnings.
-        super().__init__(record=True)  # type: ignore[call-arg]
+        super().__init__(record=True)
         self._entered = False
-        self._list: List[warnings.WarningMessage] = []
+        self._list: list[warnings.WarningMessage] = []
 
     @property
-    def list(self) -> List["warnings.WarningMessage"]:
+    def list(self) -> list[warnings.WarningMessage]:
         """The list of recorded warnings."""
         return self._list
 
-    def __getitem__(self, i: int) -> "warnings.WarningMessage":
+    def __getitem__(self, i: int) -> warnings.WarningMessage:
         """Get a recorded warning by index."""
         return self._list[i]
 
-    def __iter__(self) -> Iterator["warnings.WarningMessage"]:
+    def __iter__(self) -> Iterator[warnings.WarningMessage]:
         """Iterate through the recorded warnings."""
         return iter(self._list)
 
@@ -185,24 +203,33 @@ def __len__(self) -> int:
         """The number of recorded warnings."""
         return len(self._list)
 
-    def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage":
-        """Pop the first recorded warning, raise exception if not exists."""
+    def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage:
+        """Pop the first recorded warning which is an instance of ``cls``,
+        but not an instance of a child class of any other match.
+        Raises ``AssertionError`` if there is no match.
+        """
+        best_idx: int | None = None
         for i, w in enumerate(self._list):
-            if issubclass(w.category, cls):
-                return self._list.pop(i)
+            if w.category == cls:
+                return self._list.pop(i)  # exact match, stop looking
+            if issubclass(w.category, cls) and (
+                best_idx is None
+                or not issubclass(w.category, self._list[best_idx].category)
+            ):
+                best_idx = i
+        if best_idx is not None:
+            return self._list.pop(best_idx)
         __tracebackhide__ = True
-        raise AssertionError("%r not found in warning list" % cls)
+        raise AssertionError(f"{cls!r} not found in warning list")
 
     def clear(self) -> None:
         """Clear the list of recorded warnings."""
         self._list[:] = []
 
-    # Type ignored because it doesn't exactly warnings.catch_warnings.__enter__
-    # -- it returns a List but we only emulate one.
-    def __enter__(self) -> "WarningsRecorder":  # type: ignore
+    def __enter__(self) -> Self:
         if self._entered:
             __tracebackhide__ = True
-            raise RuntimeError("Cannot enter %r twice" % self)
+            raise RuntimeError(f"Cannot enter {self!r} twice")
         _list = super().__enter__()
         # record=True means it's None.
         assert _list is not None
@@ -212,13 +239,13 @@ def __enter__(self) -> "WarningsRecorder":  # type: ignore
 
     def __exit__(
         self,
-        exc_type: Optional[Type[BaseException]],
-        exc_val: Optional[BaseException],
-        exc_tb: Optional[TracebackType],
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
     ) -> None:
         if not self._entered:
             __tracebackhide__ = True
-            raise RuntimeError("Cannot exit %r without entering first" % self)
+            raise RuntimeError(f"Cannot exit {self!r} without entering first")
 
         super().__exit__(exc_type, exc_val, exc_tb)
 
@@ -231,10 +258,8 @@ def __exit__(
 class WarningsChecker(WarningsRecorder):
     def __init__(
         self,
-        expected_warning: Optional[
-            Union[Type[Warning], Tuple[Type[Warning], ...]]
-        ] = Warning,
-        match_expr: Optional[Union[str, Pattern[str]]] = None,
+        expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,
+        match_expr: str | re.Pattern[str] | None = None,
         *,
         _ispytest: bool = False,
     ) -> None:
@@ -242,15 +267,14 @@ def __init__(
         super().__init__(_ispytest=True)
 
         msg = "exceptions must be derived from Warning, not %s"
-        if expected_warning is None:
-            warnings.warn(WARNS_NONE_ARG, stacklevel=4)
-            expected_warning_tup = None
-        elif isinstance(expected_warning, tuple):
+        if isinstance(expected_warning, tuple):
             for exc in expected_warning:
                 if not issubclass(exc, Warning):
                     raise TypeError(msg % type(exc))
             expected_warning_tup = expected_warning
-        elif issubclass(expected_warning, Warning):
+        elif isinstance(expected_warning, type) and issubclass(
+            expected_warning, Warning
+        ):
             expected_warning_tup = (expected_warning,)
         else:
             raise TypeError(msg % type(expected_warning))
@@ -258,39 +282,84 @@ def __init__(
         self.expected_warning = expected_warning_tup
         self.match_expr = match_expr
 
+    def matches(self, warning: warnings.WarningMessage) -> bool:
+        assert self.expected_warning is not None
+        return issubclass(warning.category, self.expected_warning) and bool(
+            self.match_expr is None or re.search(self.match_expr, str(warning.message))
+        )
+
     def __exit__(
         self,
-        exc_type: Optional[Type[BaseException]],
-        exc_val: Optional[BaseException],
-        exc_tb: Optional[TracebackType],
+        exc_type: type[BaseException] | None,
+        exc_val: BaseException | None,
+        exc_tb: TracebackType | None,
     ) -> None:
         super().__exit__(exc_type, exc_val, exc_tb)
 
         __tracebackhide__ = True
 
-        # only check if we're not currently handling an exception
-        if exc_type is None and exc_val is None and exc_tb is None:
-            if self.expected_warning is not None:
-                if not any(issubclass(r.category, self.expected_warning) for r in self):
-                    __tracebackhide__ = True
-                    fail(
-                        "DID NOT WARN. No warnings of type {} were emitted. "
-                        "The list of emitted warnings is: {}.".format(
-                            self.expected_warning, [each.message for each in self]
-                        )
+        # BaseExceptions like pytest.{skip,fail,xfail,exit} or Ctrl-C within
+        # pytest.warns should *not* trigger "DID NOT WARN" and get suppressed
+        # when the warning doesn't happen. Control-flow exceptions should always
+        # propagate.
+        if exc_val is not None and (
+            not isinstance(exc_val, Exception)
+            # Exit is an Exception, not a BaseException, for some reason.
+            or isinstance(exc_val, Exit)
+        ):
+            return
+
+        def found_str() -> str:
+            return pformat([record.message for record in self], indent=2)
+
+        try:
+            if not any(issubclass(w.category, self.expected_warning) for w in self):
+                fail(
+                    f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n"
+                    f" Emitted warnings: {found_str()}."
+                )
+            elif not any(self.matches(w) for w in self):
+                fail(
+                    f"DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted.\n"
+                    f" Regex: {self.match_expr}\n"
+                    f" Emitted warnings: {found_str()}."
+                )
+        finally:
+            # Whether or not any warnings matched, we want to re-emit all unmatched warnings.
+            for w in self:
+                if not self.matches(w):
+                    warnings.warn_explicit(
+                        message=w.message,
+                        category=w.category,
+                        filename=w.filename,
+                        lineno=w.lineno,
+                        module=w.__module__,
+                        source=w.source,
                     )
-                elif self.match_expr is not None:
-                    for r in self:
-                        if issubclass(r.category, self.expected_warning):
-                            if re.compile(self.match_expr).search(str(r.message)):
-                                break
-                    else:
-                        fail(
-                            "DID NOT WARN. No warnings of type {} matching"
-                            " ('{}') were emitted. The list of emitted warnings"
-                            " is: {}.".format(
-                                self.expected_warning,
-                                self.match_expr,
-                                [each.message for each in self],
-                            )
-                        )
+
+            # Currently in Python it is possible to pass other types than an
+            # `str` message when creating `Warning` instances, however this
+            # causes an exception when :func:`warnings.filterwarnings` is used
+            # to filter those warnings. See
+            # https://github.com/python/cpython/issues/103577 for a discussion.
+            # While this can be considered a bug in CPython, we put guards in
+            # pytest as the error message produced without this check in place
+            # is confusing (#10865).
+            for w in self:
+                if type(w.message) is not UserWarning:
+                    # If the warning was of an incorrect type then `warnings.warn()`
+                    # creates a UserWarning. Any other warning must have been specified
+                    # explicitly.
+                    continue
+                if not w.message.args:
+                    # UserWarning() without arguments must have been specified explicitly.
+                    continue
+                msg = w.message.args[0]
+                if isinstance(msg, str):
+                    continue
+                # It's possible that UserWarning was explicitly specified, and
+                # its first argument was not a string. But that case can't be
+                # distinguished from an invalid type.
+                raise TypeError(
+                    f"Warning must be str or Warning, got {msg!r} (type {type(msg).__name__})"
+                )
diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py
index a68e68bc526..480ffae1f9c 100644
--- a/src/_pytest/reports.py
+++ b/src/_pytest/reports.py
@@ -1,20 +1,20 @@
-import os
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Iterable
+from collections.abc import Iterator
+from collections.abc import Mapping
+from collections.abc import Sequence
+import dataclasses
 from io import StringIO
+import os
 from pprint import pprint
 from typing import Any
 from typing import cast
-from typing import Dict
-from typing import Iterable
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import Type
+from typing import final
+from typing import Literal
+from typing import NoReturn
 from typing import TYPE_CHECKING
-from typing import TypeVar
-from typing import Union
-
-import attr
 
 from _pytest._code.code import ExceptionChainRepr
 from _pytest._code.code import ExceptionInfo
@@ -28,15 +28,15 @@
 from _pytest._code.code import ReprTraceback
 from _pytest._code.code import TerminalRepr
 from _pytest._io import TerminalWriter
-from _pytest.compat import final
 from _pytest.config import Config
 from _pytest.nodes import Collector
 from _pytest.nodes import Item
+from _pytest.outcomes import fail
 from _pytest.outcomes import skip
 
+
 if TYPE_CHECKING:
-    from typing import NoReturn
-    from typing_extensions import Literal
+    from typing_extensions import Self
 
     from _pytest.runner import CallInfo
 
@@ -46,33 +46,29 @@ def getworkerinfoline(node):
         return node._workerinfocache
     except AttributeError:
         d = node.workerinfo
-        ver = "%s.%s.%s" % d["version_info"][:3]
+        ver = "{}.{}.{}".format(*d["version_info"][:3])
         node._workerinfocache = s = "[{}] {} -- Python {} {}".format(
             d["id"], d["sysplatform"], ver, d["executable"]
         )
         return s
 
 
-_R = TypeVar("_R", bound="BaseReport")
-
-
 class BaseReport:
-    when: Optional[str]
-    location: Optional[Tuple[str, Optional[int], str]]
-    longrepr: Union[
-        None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
-    ]
-    sections: List[Tuple[str, str]]
+    when: str | None
+    location: tuple[str, int | None, str] | None
+    longrepr: (
+        None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr
+    )
+    sections: list[tuple[str, str]]
     nodeid: str
-    outcome: "Literal['passed', 'failed', 'skipped']"
+    outcome: Literal["passed", "failed", "skipped"]
 
     def __init__(self, **kw: Any) -> None:
         self.__dict__.update(kw)
 
     if TYPE_CHECKING:
         # Can have arbitrary fields given to __init__().
-        def __getattr__(self, key: str) -> Any:
-            ...
+        def __getattr__(self, key: str) -> Any: ...
 
     def toterminal(self, out: TerminalWriter) -> None:
         if hasattr(self, "node"):
@@ -94,7 +90,7 @@ def toterminal(self, out: TerminalWriter) -> None:
                 s = "<unprintable longrepr>"
             out.line(s)
 
-    def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]:
+    def get_sections(self, prefix: str) -> Iterator[tuple[str, str]]:
         for name, content in self.sections:
             if name.startswith(prefix):
                 yield prefix, content
@@ -176,7 +172,7 @@ def count_towards_summary(self) -> bool:
         return True
 
     @property
-    def head_line(self) -> Optional[str]:
+    def head_line(self) -> str | None:
         """**Experimental** The head line shown with longrepr output for this
         report, more commonly during traceback representation during
         failures::
@@ -196,13 +192,28 @@ def head_line(self) -> Optional[str]:
             return domain
         return None
 
-    def _get_verbose_word(self, config: Config):
+    def _get_verbose_word_with_markup(
+        self, config: Config, default_markup: Mapping[str, bool]
+    ) -> tuple[str, Mapping[str, bool]]:
         _category, _short, verbose = config.hook.pytest_report_teststatus(
             report=self, config=config
         )
-        return verbose
 
-    def _to_json(self) -> Dict[str, Any]:
+        if isinstance(verbose, str):
+            return verbose, default_markup
+
+        if isinstance(verbose, Sequence) and len(verbose) == 2:
+            word, markup = verbose
+            if isinstance(word, str) and isinstance(markup, Mapping):
+                return word, markup
+
+        fail(  # pragma: no cover
+            "pytest_report_teststatus() hook (from a plugin) returned "
+            f"an invalid verbose value: {verbose!r}.\nExpected either a string "
+            "or a tuple of (word, markup)."
+        )
+
+    def _to_json(self) -> dict[str, Any]:
         """Return the contents of this report as a dict of builtin entries,
         suitable for serialization.
 
@@ -213,7 +224,7 @@ def _to_json(self) -> Dict[str, Any]:
         return _report_to_json(self)
 
     @classmethod
-    def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R:
+    def _from_json(cls, reportdict: dict[str, object]) -> Self:
         """Create either a TestReport or CollectReport, depending on the calling class.
 
         It is the callers responsibility to know which class to pass here.
@@ -227,15 +238,15 @@ def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R:
 
 
 def _report_unserialization_failure(
-    type_name: str, report_class: Type[BaseReport], reportdict
-) -> "NoReturn":
+    type_name: str, report_class: type[BaseReport], reportdict
+) -> NoReturn:
     url = "https://github.com/pytest-dev/pytest/issues"
     stream = StringIO()
     pprint("-" * 100, stream=stream)
-    pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream)
-    pprint("report_name: %s" % report_class, stream=stream)
+    pprint(f"INTERNALERROR: Unknown entry type returned: {type_name}", stream=stream)
+    pprint(f"report_name: {report_class}", stream=stream)
     pprint(reportdict, stream=stream)
-    pprint("Please report this bug at %s" % url, stream=stream)
+    pprint(f"Please report this bug at {url}", stream=stream)
     pprint("-" * 100, stream=stream)
     raise RuntimeError(stream.getvalue())
 
@@ -250,19 +261,27 @@ class TestReport(BaseReport):
 
     __test__ = False
 
+    # Defined by skipping plugin.
+    # xfail reason if xfailed, otherwise not defined. Use hasattr to distinguish.
+    wasxfail: str
+
     def __init__(
         self,
         nodeid: str,
-        location: Tuple[str, Optional[int], str],
-        keywords,
-        outcome: "Literal['passed', 'failed', 'skipped']",
-        longrepr: Union[
-            None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
-        ],
-        when: "Literal['setup', 'call', 'teardown']",
-        sections: Iterable[Tuple[str, str]] = (),
+        location: tuple[str, int | None, str],
+        keywords: Mapping[str, Any],
+        outcome: Literal["passed", "failed", "skipped"],
+        longrepr: None
+        | ExceptionInfo[BaseException]
+        | tuple[str, int, str]
+        | str
+        | TerminalRepr,
+        when: Literal["setup", "call", "teardown"],
+        sections: Iterable[tuple[str, str]] = (),
         duration: float = 0,
-        user_properties: Optional[Iterable[Tuple[str, object]]] = None,
+        start: float = 0,
+        stop: float = 0,
+        user_properties: Iterable[tuple[str, object]] | None = None,
         **extra,
     ) -> None:
         #: Normalized collection nodeid.
@@ -271,11 +290,13 @@ def __init__(
         #: A (filesystempath, lineno, domaininfo) tuple indicating the
         #: actual location of a test item - it might be different from the
         #: collected one e.g. if a method is inherited from a different module.
-        self.location: Tuple[str, Optional[int], str] = location
+        #: The filesystempath may be relative to ``config.rootdir``.
+        #: The line number is 0-based.
+        self.location: tuple[str, int | None, str] = location
 
         #: A name -> value dictionary containing all keywords and
         #: markers associated with a test invocation.
-        self.keywords = keywords
+        self.keywords: Mapping[str, Any] = keywords
 
         #: Test outcome, always one of "passed", "failed", "skipped".
         self.outcome = outcome
@@ -284,7 +305,7 @@ def __init__(
         self.longrepr = longrepr
 
         #: One of 'setup', 'call', 'teardown' to indicate runtest phase.
-        self.when = when
+        self.when: Literal["setup", "call", "teardown"] = when
 
         #: User properties is a list of tuples (name, value) that holds user
         #: defined properties of the test.
@@ -297,34 +318,43 @@ def __init__(
         self.sections = list(sections)
 
         #: Time it took to run just the test.
-        self.duration = duration
+        self.duration: float = duration
+
+        #: The system time when the call started, in seconds since the epoch.
+        self.start: float = start
+        #: The system time when the call ended, in seconds since the epoch.
+        self.stop: float = stop
 
         self.__dict__.update(extra)
 
     def __repr__(self) -> str:
-        return "<{} {!r} when={!r} outcome={!r}>".format(
-            self.__class__.__name__, self.nodeid, self.when, self.outcome
-        )
+        return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>"
 
     @classmethod
-    def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport":
-        """Create and fill a TestReport with standard item and call info."""
+    def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport:
+        """Create and fill a TestReport with standard item and call info.
+
+        :param item: The item.
+        :param call: The call info.
+        """
         when = call.when
         # Remove "collect" from the Literal type -- only for collection calls.
         assert when != "collect"
         duration = call.duration
+        start = call.start
+        stop = call.stop
         keywords = {x: 1 for x in item.keywords}
         excinfo = call.excinfo
         sections = []
         if not call.excinfo:
             outcome: Literal["passed", "failed", "skipped"] = "passed"
-            longrepr: Union[
-                None,
-                ExceptionInfo[BaseException],
-                Tuple[str, int, str],
-                str,
-                TerminalRepr,
-            ] = None
+            longrepr: (
+                None
+                | ExceptionInfo[BaseException]
+                | tuple[str, int, str]
+                | str
+                | TerminalRepr
+            ) = None
         else:
             if not isinstance(excinfo, ExceptionInfo):
                 outcome = "failed"
@@ -332,6 +362,9 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport":
             elif isinstance(excinfo.value, skip.Exception):
                 outcome = "skipped"
                 r = excinfo._getreprcrash()
+                assert r is not None, (
+                    "There should always be a traceback entry for skipping a test."
+                )
                 if excinfo.value._use_item_location:
                     path, line = item.reportinfo()[:2]
                     assert line is not None
@@ -357,6 +390,8 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport":
             when,
             sections,
             duration,
+            start,
+            stop,
             user_properties=item.user_properties,
         )
 
@@ -373,12 +408,14 @@ class CollectReport(BaseReport):
     def __init__(
         self,
         nodeid: str,
-        outcome: "Literal['passed', 'failed', 'skipped']",
-        longrepr: Union[
-            None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
-        ],
-        result: Optional[List[Union[Item, Collector]]],
-        sections: Iterable[Tuple[str, str]] = (),
+        outcome: Literal["passed", "failed", "skipped"],
+        longrepr: None
+        | ExceptionInfo[BaseException]
+        | tuple[str, int, str]
+        | str
+        | TerminalRepr,
+        result: list[Item | Collector] | None,
+        sections: Iterable[tuple[str, str]] = (),
         **extra,
     ) -> None:
         #: Normalized collection nodeid.
@@ -402,13 +439,13 @@ def __init__(
         self.__dict__.update(extra)
 
     @property
-    def location(self):
+    def location(  # type:ignore[override]
+        self,
+    ) -> tuple[str, int | None, str] | None:
         return (self.fspath, None, self.fspath)
 
     def __repr__(self) -> str:
-        return "<CollectReport {!r} lenresult={} outcome={!r}>".format(
-            self.nodeid, len(self.result), self.outcome
-        )
+        return f"<CollectReport {self.nodeid!r} lenresult={len(self.result)} outcome={self.outcome!r}>"
 
 
 class CollectErrorRepr(TerminalRepr):
@@ -420,8 +457,8 @@ def toterminal(self, out: TerminalWriter) -> None:
 
 
 def pytest_report_to_serializable(
-    report: Union[CollectReport, TestReport]
-) -> Optional[Dict[str, Any]]:
+    report: CollectReport | TestReport,
+) -> dict[str, Any] | None:
     if isinstance(report, (TestReport, CollectReport)):
         data = report._to_json()
         data["$report_type"] = report.__class__.__name__
@@ -431,8 +468,8 @@ def pytest_report_to_serializable(
 
 
 def pytest_report_from_serializable(
-    data: Dict[str, Any],
-) -> Optional[Union[CollectReport, TestReport]]:
+    data: dict[str, Any],
+) -> CollectReport | TestReport | None:
     if "$report_type" in data:
         if data["$report_type"] == "TestReport":
             return TestReport._from_json(data)
@@ -444,7 +481,7 @@ def pytest_report_from_serializable(
     return None
 
 
-def _report_to_json(report: BaseReport) -> Dict[str, Any]:
+def _report_to_json(report: BaseReport) -> dict[str, Any]:
     """Return the contents of this report as a dict of builtin entries,
     suitable for serialization.
 
@@ -452,35 +489,35 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
     """
 
     def serialize_repr_entry(
-        entry: Union[ReprEntry, ReprEntryNative]
-    ) -> Dict[str, Any]:
-        data = attr.asdict(entry)
+        entry: ReprEntry | ReprEntryNative,
+    ) -> dict[str, Any]:
+        data = dataclasses.asdict(entry)
         for key, value in data.items():
             if hasattr(value, "__dict__"):
-                data[key] = attr.asdict(value)
+                data[key] = dataclasses.asdict(value)
         entry_data = {"type": type(entry).__name__, "data": data}
         return entry_data
 
-    def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]:
-        result = attr.asdict(reprtraceback)
+    def serialize_repr_traceback(reprtraceback: ReprTraceback) -> dict[str, Any]:
+        result = dataclasses.asdict(reprtraceback)
         result["reprentries"] = [
             serialize_repr_entry(x) for x in reprtraceback.reprentries
         ]
         return result
 
     def serialize_repr_crash(
-        reprcrash: Optional[ReprFileLocation],
-    ) -> Optional[Dict[str, Any]]:
+        reprcrash: ReprFileLocation | None,
+    ) -> dict[str, Any] | None:
         if reprcrash is not None:
-            return attr.asdict(reprcrash)
+            return dataclasses.asdict(reprcrash)
         else:
             return None
 
-    def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]:
+    def serialize_exception_longrepr(rep: BaseReport) -> dict[str, Any]:
         assert rep.longrepr is not None
         # TODO: Investigate whether the duck typing is really necessary here.
         longrepr = cast(ExceptionRepr, rep.longrepr)
-        result: Dict[str, Any] = {
+        result: dict[str, Any] = {
             "reprcrash": serialize_repr_crash(longrepr.reprcrash),
             "reprtraceback": serialize_repr_traceback(longrepr.reprtraceback),
             "sections": longrepr.sections,
@@ -517,7 +554,7 @@ def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]:
     return d
 
 
-def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
+def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]:
     """Return **kwargs that can be used to construct a TestReport or
     CollectReport instance.
 
@@ -538,7 +575,7 @@ def deserialize_repr_entry(entry_data):
             if data["reprlocals"]:
                 reprlocals = ReprLocals(data["reprlocals"]["lines"])
 
-            reprentry: Union[ReprEntry, ReprEntryNative] = ReprEntry(
+            reprentry: ReprEntry | ReprEntryNative = ReprEntry(
                 lines=data["lines"],
                 reprfuncargs=reprfuncargs,
                 reprlocals=reprlocals,
@@ -557,7 +594,7 @@ def deserialize_repr_traceback(repr_traceback_dict):
         ]
         return ReprTraceback(**repr_traceback_dict)
 
-    def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]):
+    def deserialize_repr_crash(repr_crash_dict: dict[str, Any] | None):
         if repr_crash_dict is not None:
             return ReprFileLocation(**repr_crash_dict)
         else:
@@ -568,7 +605,6 @@ def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]):
         and "reprcrash" in reportdict["longrepr"]
         and "reprtraceback" in reportdict["longrepr"]
     ):
-
         reprtraceback = deserialize_repr_traceback(
             reportdict["longrepr"]["reprtraceback"]
         )
@@ -585,11 +621,14 @@ def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]):
                         description,
                     )
                 )
-            exception_info: Union[
-                ExceptionChainRepr, ReprExceptionInfo
-            ] = ExceptionChainRepr(chain)
+            exception_info: ExceptionChainRepr | ReprExceptionInfo = ExceptionChainRepr(
+                chain
+            )
         else:
-            exception_info = ReprExceptionInfo(reprtraceback, reprcrash)
+            exception_info = ReprExceptionInfo(
+                reprtraceback=reprtraceback,
+                reprcrash=reprcrash,
+            )
 
         for section in reportdict["longrepr"]["sections"]:
             exception_info.addsection(*section)
diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py
index e43dd2dc818..f0543289267 100644
--- a/src/_pytest/runner.py
+++ b/src/_pytest/runner.py
@@ -1,21 +1,20 @@
+# mypy: allow-untyped-defs
 """Basic collect and runtest protocol implementations."""
+
+from __future__ import annotations
+
 import bdb
+from collections.abc import Callable
+import dataclasses
 import os
 import sys
-import warnings
-from typing import Callable
+import types
 from typing import cast
-from typing import Dict
+from typing import final
 from typing import Generic
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import Type
+from typing import Literal
 from typing import TYPE_CHECKING
 from typing import TypeVar
-from typing import Union
-
-import attr
 
 from .reports import BaseReport
 from .reports import CollectErrorRepr
@@ -25,11 +24,10 @@
 from _pytest._code.code import ExceptionChainRepr
 from _pytest._code.code import ExceptionInfo
 from _pytest._code.code import TerminalRepr
-from _pytest.compat import final
 from _pytest.config.argparsing import Parser
 from _pytest.deprecated import check_ispytest
-from _pytest.deprecated import UNITTEST_SKIP_DURING_COLLECTION
 from _pytest.nodes import Collector
+from _pytest.nodes import Directory
 from _pytest.nodes import Item
 from _pytest.nodes import Node
 from _pytest.outcomes import Exit
@@ -37,9 +35,11 @@
 from _pytest.outcomes import Skipped
 from _pytest.outcomes import TEST_OUTCOME
 
-if TYPE_CHECKING:
-    from typing_extensions import Literal
 
+if sys.version_info < (3, 11):
+    from exceptiongroup import BaseExceptionGroup
+
+if TYPE_CHECKING:
     from _pytest.main import Session
     from _pytest.terminal import TerminalReporter
 
@@ -48,31 +48,34 @@
 
 
 def pytest_addoption(parser: Parser) -> None:
-    group = parser.getgroup("terminal reporting", "reporting", after="general")
+    group = parser.getgroup("terminal reporting", "Reporting", after="general")
     group.addoption(
         "--durations",
         action="store",
         type=int,
         default=None,
         metavar="N",
-        help="show N slowest setup/test durations (N=0 for all).",
+        help="Show N slowest setup/test durations (N=0 for all)",
     )
     group.addoption(
         "--durations-min",
         action="store",
         type=float,
-        default=0.005,
+        default=None,
         metavar="N",
-        help="Minimal duration in seconds for inclusion in slowest list. Default 0.005",
+        help="Minimal duration in seconds for inclusion in slowest list. "
+        "Default: 0.005 (or 0.0 if -vv is given).",
     )
 
 
-def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None:
+def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None:
     durations = terminalreporter.config.option.durations
     durations_min = terminalreporter.config.option.durations_min
-    verbose = terminalreporter.config.getvalue("verbose")
+    verbose = terminalreporter.config.get_verbosity()
     if durations is None:
         return
+    if durations_min is None:
+        durations_min = 0.005 if verbose < 2 else 0.0
     tr = terminalreporter
     dlist = []
     for replist in tr.stats.values():
@@ -81,33 +84,34 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None:
                 dlist.append(rep)
     if not dlist:
         return
-    dlist.sort(key=lambda x: x.duration, reverse=True)  # type: ignore[no-any-return]
+    dlist.sort(key=lambda x: x.duration, reverse=True)
     if not durations:
         tr.write_sep("=", "slowest durations")
     else:
-        tr.write_sep("=", "slowest %s durations" % durations)
+        tr.write_sep("=", f"slowest {durations} durations")
         dlist = dlist[:durations]
 
     for i, rep in enumerate(dlist):
-        if verbose < 2 and rep.duration < durations_min:
+        if rep.duration < durations_min:
             tr.write_line("")
-            tr.write_line(
-                "(%s durations < %gs hidden.  Use -vv to show these durations.)"
-                % (len(dlist) - i, durations_min)
-            )
+            message = f"({len(dlist) - i} durations < {durations_min:g}s hidden."
+            if terminalreporter.config.option.durations_min is None:
+                message += "  Use -vv to show these durations."
+            message += ")"
+            tr.write_line(message)
             break
         tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}")
 
 
-def pytest_sessionstart(session: "Session") -> None:
+def pytest_sessionstart(session: Session) -> None:
     session._setupstate = SetupState()
 
 
-def pytest_sessionfinish(session: "Session") -> None:
+def pytest_sessionfinish(session: Session) -> None:
     session._setupstate.teardown_exact(None)
 
 
-def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool:
+def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> bool:
     ihook = item.ihook
     ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location)
     runtestprotocol(item, nextitem=nextitem)
@@ -116,8 +120,8 @@ def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool:
 
 
 def runtestprotocol(
-    item: Item, log: bool = True, nextitem: Optional[Item] = None
-) -> List[TestReport]:
+    item: Item, log: bool = True, nextitem: Item | None = None
+) -> list[TestReport]:
     hasrequest = hasattr(item, "_request")
     if hasrequest and not item._request:  # type: ignore[attr-defined]
         # This only happens if the item is re-run, as is done by
@@ -130,6 +134,10 @@ def runtestprotocol(
             show_test_item(item)
         if not item.config.getoption("setuponly", False):
             reports.append(call_and_report(item, "call", log))
+    # If the session is about to fail or stop, teardown everything - this is
+    # necessary to correctly report fixture teardown errors (see #11706)
+    if item.session.shouldfail or item.session.shouldstop:
+        nextitem = None
     reports.append(call_and_report(item, "teardown", log, nextitem=nextitem))
     # After all teardown hooks have been called
     # want funcargs and request info to go away.
@@ -162,6 +170,8 @@ def pytest_runtest_call(item: Item) -> None:
         del sys.last_type
         del sys.last_value
         del sys.last_traceback
+        if sys.version_info >= (3, 12, 0):
+            del sys.last_exc  # type:ignore[attr-defined]
     except AttributeError:
         pass
     try:
@@ -170,20 +180,22 @@ def pytest_runtest_call(item: Item) -> None:
         # Store trace info to allow postmortem debugging
         sys.last_type = type(e)
         sys.last_value = e
+        if sys.version_info >= (3, 12, 0):
+            sys.last_exc = e  # type:ignore[attr-defined]
         assert e.__traceback__ is not None
         # Skip *this* frame
         sys.last_traceback = e.__traceback__.tb_next
-        raise e
+        raise
 
 
-def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None:
+def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None:
     _update_current_test_var(item, "teardown")
     item.session._setupstate.teardown_exact(nextitem)
     _update_current_test_var(item, None)
 
 
 def _update_current_test_var(
-    item: Item, when: Optional["Literal['setup', 'call', 'teardown']"]
+    item: Item, when: Literal["setup", "call", "teardown"] | None
 ) -> None:
     """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage.
 
@@ -199,7 +211,7 @@ def _update_current_test_var(
         os.environ.pop(var_name)
 
 
-def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]:
+def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None:
     if report.when in ("setup", "teardown"):
         if report.failed:
             #      category, shortletter, verbose-word
@@ -216,19 +228,32 @@ def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str
 
 
 def call_and_report(
-    item: Item, when: "Literal['setup', 'call', 'teardown']", log: bool = True, **kwds
+    item: Item, when: Literal["setup", "call", "teardown"], log: bool = True, **kwds
 ) -> TestReport:
-    call = call_runtest_hook(item, when, **kwds)
-    hook = item.ihook
-    report: TestReport = hook.pytest_runtest_makereport(item=item, call=call)
+    ihook = item.ihook
+    if when == "setup":
+        runtest_hook: Callable[..., None] = ihook.pytest_runtest_setup
+    elif when == "call":
+        runtest_hook = ihook.pytest_runtest_call
+    elif when == "teardown":
+        runtest_hook = ihook.pytest_runtest_teardown
+    else:
+        assert False, f"Unhandled runtest hook case: {when}"
+    reraise: tuple[type[BaseException], ...] = (Exit,)
+    if not item.config.getoption("usepdb", False):
+        reraise += (KeyboardInterrupt,)
+    call = CallInfo.from_call(
+        lambda: runtest_hook(item=item, **kwds), when=when, reraise=reraise
+    )
+    report: TestReport = ihook.pytest_runtest_makereport(item=item, call=call)
     if log:
-        hook.pytest_runtest_logreport(report=report)
+        ihook.pytest_runtest_logreport(report=report)
     if check_interactive_exception(call, report):
-        hook.pytest_exception_interact(node=item, call=call, report=report)
+        ihook.pytest_exception_interact(node=item, call=call, report=report)
     return report
 
 
-def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> bool:
+def check_interactive_exception(call: CallInfo[object], report: BaseReport) -> bool:
     """Check whether the call raised an exception that should be reported as
     interactive."""
     if call.excinfo is None:
@@ -243,36 +268,17 @@ def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) ->
     return True
 
 
-def call_runtest_hook(
-    item: Item, when: "Literal['setup', 'call', 'teardown']", **kwds
-) -> "CallInfo[None]":
-    if when == "setup":
-        ihook: Callable[..., None] = item.ihook.pytest_runtest_setup
-    elif when == "call":
-        ihook = item.ihook.pytest_runtest_call
-    elif when == "teardown":
-        ihook = item.ihook.pytest_runtest_teardown
-    else:
-        assert False, f"Unhandled runtest hook case: {when}"
-    reraise: Tuple[Type[BaseException], ...] = (Exit,)
-    if not item.config.getoption("usepdb", False):
-        reraise += (KeyboardInterrupt,)
-    return CallInfo.from_call(
-        lambda: ihook(item=item, **kwds), when=when, reraise=reraise
-    )
-
-
 TResult = TypeVar("TResult", covariant=True)
 
 
 @final
-@attr.s(repr=False, init=False, auto_attribs=True)
+@dataclasses.dataclass
 class CallInfo(Generic[TResult]):
     """Result/Exception info of a function invocation."""
 
-    _result: Optional[TResult]
+    _result: TResult | None
     #: The captured exception of the call, if it raised.
-    excinfo: Optional[ExceptionInfo[BaseException]]
+    excinfo: ExceptionInfo[BaseException] | None
     #: The system time when the call started, in seconds since the epoch.
     start: float
     #: The system time when the call ended, in seconds since the epoch.
@@ -280,16 +286,16 @@ class CallInfo(Generic[TResult]):
     #: The call duration, in seconds.
     duration: float
     #: The context of invocation: "collect", "setup", "call" or "teardown".
-    when: "Literal['collect', 'setup', 'call', 'teardown']"
+    when: Literal["collect", "setup", "call", "teardown"]
 
     def __init__(
         self,
-        result: Optional[TResult],
-        excinfo: Optional[ExceptionInfo[BaseException]],
+        result: TResult | None,
+        excinfo: ExceptionInfo[BaseException] | None,
         start: float,
         stop: float,
         duration: float,
-        when: "Literal['collect', 'setup', 'call', 'teardown']",
+        when: Literal["collect", "setup", "call", "teardown"],
         *,
         _ispytest: bool = False,
     ) -> None:
@@ -317,16 +323,15 @@ def result(self) -> TResult:
     @classmethod
     def from_call(
         cls,
-        func: "Callable[[], TResult]",
-        when: "Literal['collect', 'setup', 'call', 'teardown']",
-        reraise: Optional[
-            Union[Type[BaseException], Tuple[Type[BaseException], ...]]
-        ] = None,
-    ) -> "CallInfo[TResult]":
+        func: Callable[[], TResult],
+        when: Literal["collect", "setup", "call", "teardown"],
+        reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None,
+    ) -> CallInfo[TResult]:
         """Call func, wrapping the result in a CallInfo.
 
         :param func:
             The function to call. Called without arguments.
+        :type func: Callable[[], _pytest.runner.TResult]
         :param when:
             The phase in which the function is called.
         :param reraise:
@@ -337,7 +342,7 @@ def from_call(
         start = timing.time()
         precise_start = timing.perf_counter()
         try:
-            result: Optional[TResult] = func()
+            result: TResult | None = func()
         except BaseException:
             excinfo = ExceptionInfo.from_current()
             if reraise is not None and isinstance(excinfo.value, reraise):
@@ -368,22 +373,37 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport:
 
 
 def pytest_make_collect_report(collector: Collector) -> CollectReport:
-    call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
-    longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None
+    def collect() -> list[Item | Collector]:
+        # Before collecting, if this is a Directory, load the conftests.
+        # If a conftest import fails to load, it is considered a collection
+        # error of the Directory collector. This is why it's done inside of the
+        # CallInfo wrapper.
+        #
+        # Note: initial conftests are loaded early, not here.
+        if isinstance(collector, Directory):
+            collector.config.pluginmanager._loadconftestmodules(
+                collector.path,
+                collector.config.getoption("importmode"),
+                rootpath=collector.config.rootpath,
+                consider_namespace_packages=collector.config.getini(
+                    "consider_namespace_packages"
+                ),
+            )
+
+        return list(collector.collect())
+
+    call = CallInfo.from_call(
+        collect, "collect", reraise=(KeyboardInterrupt, SystemExit)
+    )
+    longrepr: None | tuple[str, int, str] | str | TerminalRepr = None
     if not call.excinfo:
         outcome: Literal["passed", "skipped", "failed"] = "passed"
     else:
         skip_exceptions = [Skipped]
         unittest = sys.modules.get("unittest")
         if unittest is not None:
-            # Type ignored because unittest is loaded dynamically.
-            skip_exceptions.append(unittest.SkipTest)  # type: ignore
+            skip_exceptions.append(unittest.SkipTest)
         if isinstance(call.excinfo.value, tuple(skip_exceptions)):
-            if unittest is not None and isinstance(
-                call.excinfo.value, unittest.SkipTest  # type: ignore[attr-defined]
-            ):
-                warnings.warn(UNITTEST_SKIP_DURING_COLLECTION, stacklevel=2)
-
             outcome = "skipped"
             r_ = collector._repr_failure_py(call.excinfo, "line")
             assert isinstance(r_, ExceptionChainRepr), repr(r_)
@@ -469,13 +489,13 @@ class SetupState:
 
     def __init__(self) -> None:
         # The stack is in the dict insertion order.
-        self.stack: Dict[
+        self.stack: dict[
             Node,
-            Tuple[
+            tuple[
                 # Node's finalizers.
-                List[Callable[[], object]],
-                # Node's exception, if its setup raised.
-                Optional[Union[OutcomeException, Exception]],
+                list[Callable[[], object]],
+                # Node's exception and original traceback, if its setup raised.
+                tuple[OutcomeException | Exception, types.TracebackType | None] | None,
             ],
         ] = {}
 
@@ -488,7 +508,7 @@ def setup(self, item: Item) -> None:
         for col, (finalizers, exc) in self.stack.items():
             assert col in needed_collectors, "previous item was not torn down properly"
             if exc:
-                raise exc
+                raise exc[0].with_traceback(exc[1])
 
         for col in needed_collectors[len(self.stack) :]:
             assert col not in self.stack
@@ -497,8 +517,8 @@ def setup(self, item: Item) -> None:
             try:
                 col.setup()
             except TEST_OUTCOME as exc:
-                self.stack[col] = (self.stack[col][0], exc)
-                raise exc
+                self.stack[col] = (self.stack[col][0], (exc, exc.__traceback__))
+                raise
 
     def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None:
         """Attach a finalizer to the given node.
@@ -510,30 +530,37 @@ def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None:
         assert node in self.stack, (node, self.stack)
         self.stack[node][0].append(finalizer)
 
-    def teardown_exact(self, nextitem: Optional[Item]) -> None:
+    def teardown_exact(self, nextitem: Item | None) -> None:
         """Teardown the current stack up until reaching nodes that nextitem
         also descends from.
 
         When nextitem is None (meaning we're at the last item), the entire
         stack is torn down.
         """
-        needed_collectors = nextitem and nextitem.listchain() or []
-        exc = None
+        needed_collectors = (nextitem and nextitem.listchain()) or []
+        exceptions: list[BaseException] = []
         while self.stack:
             if list(self.stack.keys()) == needed_collectors[: len(self.stack)]:
                 break
             node, (finalizers, _) = self.stack.popitem()
+            these_exceptions = []
             while finalizers:
                 fin = finalizers.pop()
                 try:
                     fin()
                 except TEST_OUTCOME as e:
-                    # XXX Only first exception will be seen by user,
-                    #     ideally all should be reported.
-                    if exc is None:
-                        exc = e
-        if exc:
-            raise exc
+                    these_exceptions.append(e)
+
+            if len(these_exceptions) == 1:
+                exceptions.extend(these_exceptions)
+            elif these_exceptions:
+                msg = f"errors while tearing down {node!r}"
+                exceptions.append(BaseExceptionGroup(msg, these_exceptions[::-1]))
+
+        if len(exceptions) == 1:
+            raise exceptions[0]
+        elif exceptions:
+            raise BaseExceptionGroup("errors during test teardown", exceptions[::-1])
         if nextitem is None:
             assert not self.stack
 
diff --git a/src/_pytest/scope.py b/src/_pytest/scope.py
index 7a746fb9fa9..2b007e87893 100644
--- a/src/_pytest/scope.py
+++ b/src/_pytest/scope.py
@@ -7,15 +7,15 @@
 
 Also this makes the module light to import, as it should.
 """
+
+from __future__ import annotations
+
 from enum import Enum
 from functools import total_ordering
-from typing import Optional
-from typing import TYPE_CHECKING
+from typing import Literal
 
-if TYPE_CHECKING:
-    from typing_extensions import Literal
 
-    _ScopeName = Literal["session", "package", "module", "class", "function"]
+_ScopeName = Literal["session", "package", "module", "class", "function"]
 
 
 @total_ordering
@@ -33,35 +33,35 @@ class Scope(Enum):
     """
 
     # Scopes need to be listed from lower to higher.
-    Function: "_ScopeName" = "function"
-    Class: "_ScopeName" = "class"
-    Module: "_ScopeName" = "module"
-    Package: "_ScopeName" = "package"
-    Session: "_ScopeName" = "session"
+    Function = "function"
+    Class = "class"
+    Module = "module"
+    Package = "package"
+    Session = "session"
 
-    def next_lower(self) -> "Scope":
+    def next_lower(self) -> Scope:
         """Return the next lower scope."""
         index = _SCOPE_INDICES[self]
         if index == 0:
             raise ValueError(f"{self} is the lower-most scope")
         return _ALL_SCOPES[index - 1]
 
-    def next_higher(self) -> "Scope":
+    def next_higher(self) -> Scope:
         """Return the next higher scope."""
         index = _SCOPE_INDICES[self]
         if index == len(_SCOPE_INDICES) - 1:
             raise ValueError(f"{self} is the upper-most scope")
         return _ALL_SCOPES[index + 1]
 
-    def __lt__(self, other: "Scope") -> bool:
+    def __lt__(self, other: Scope) -> bool:
         self_index = _SCOPE_INDICES[self]
         other_index = _SCOPE_INDICES[other]
         return self_index < other_index
 
     @classmethod
     def from_user(
-        cls, scope_name: "_ScopeName", descr: str, where: Optional[str] = None
-    ) -> "Scope":
+        cls, scope_name: _ScopeName, descr: str, where: str | None = None
+    ) -> Scope:
         """
         Given a scope name from the user, return the equivalent Scope enum. Should be used
         whenever we want to convert a user provided scope name to its enum object.
diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py
index 531131ce726..7e6b46bcdb4 100644
--- a/src/_pytest/setuponly.py
+++ b/src/_pytest/setuponly.py
@@ -1,8 +1,7 @@
-from typing import Generator
-from typing import Optional
-from typing import Union
+from __future__ import annotations
+
+from collections.abc import Generator
 
-import pytest
 from _pytest._io.saferepr import saferepr
 from _pytest.config import Config
 from _pytest.config import ExitCode
@@ -10,6 +9,7 @@
 from _pytest.fixtures import FixtureDef
 from _pytest.fixtures import SubRequest
 from _pytest.scope import Scope
+import pytest
 
 
 def pytest_addoption(parser: Parser) -> None:
@@ -18,47 +18,52 @@ def pytest_addoption(parser: Parser) -> None:
         "--setuponly",
         "--setup-only",
         action="store_true",
-        help="only setup fixtures, do not execute tests.",
+        help="Only setup fixtures, do not execute tests",
     )
     group.addoption(
         "--setupshow",
         "--setup-show",
         action="store_true",
-        help="show setup of fixtures while executing tests.",
+        help="Show setup of fixtures while executing tests",
     )
 
 
-@pytest.hookimpl(hookwrapper=True)
+@pytest.hookimpl(wrapper=True)
 def pytest_fixture_setup(
     fixturedef: FixtureDef[object], request: SubRequest
-) -> Generator[None, None, None]:
-    yield
-    if request.config.option.setupshow:
-        if hasattr(request, "param"):
-            # Save the fixture parameter so ._show_fixture_action() can
-            # display it now and during the teardown (in .finish()).
-            if fixturedef.ids:
-                if callable(fixturedef.ids):
-                    param = fixturedef.ids(request.param)
+) -> Generator[None, object, object]:
+    try:
+        return (yield)
+    finally:
+        if request.config.option.setupshow:
+            if hasattr(request, "param"):
+                # Save the fixture parameter so ._show_fixture_action() can
+                # display it now and during the teardown (in .finish()).
+                if fixturedef.ids:
+                    if callable(fixturedef.ids):
+                        param = fixturedef.ids(request.param)
+                    else:
+                        param = fixturedef.ids[request.param_index]
                 else:
-                    param = fixturedef.ids[request.param_index]
-            else:
-                param = request.param
-            fixturedef.cached_param = param  # type: ignore[attr-defined]
-        _show_fixture_action(fixturedef, "SETUP")
+                    param = request.param
+                fixturedef.cached_param = param  # type: ignore[attr-defined]
+            _show_fixture_action(fixturedef, request.config, "SETUP")
 
 
-def pytest_fixture_post_finalizer(fixturedef: FixtureDef[object]) -> None:
+def pytest_fixture_post_finalizer(
+    fixturedef: FixtureDef[object], request: SubRequest
+) -> None:
     if fixturedef.cached_result is not None:
-        config = fixturedef._fixturemanager.config
+        config = request.config
         if config.option.setupshow:
-            _show_fixture_action(fixturedef, "TEARDOWN")
+            _show_fixture_action(fixturedef, request.config, "TEARDOWN")
             if hasattr(fixturedef, "cached_param"):
-                del fixturedef.cached_param  # type: ignore[attr-defined]
+                del fixturedef.cached_param
 
 
-def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None:
-    config = fixturedef._fixturemanager.config
+def _show_fixture_action(
+    fixturedef: FixtureDef[object], config: Config, msg: str
+) -> None:
     capman = config.pluginmanager.getplugin("capturemanager")
     if capman:
         capman.suspend_global_capture()
@@ -68,13 +73,9 @@ def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None:
     # Use smaller indentation the higher the scope: Session = 0, Package = 1, etc.
     scope_indent = list(reversed(Scope)).index(fixturedef._scope)
     tw.write(" " * 2 * scope_indent)
-    tw.write(
-        "{step} {scope} {fixture}".format(
-            step=msg.ljust(8),  # align the output to TEARDOWN
-            scope=fixturedef.scope[0].upper(),
-            fixture=fixturedef.argname,
-        )
-    )
+
+    scopename = fixturedef.scope[0].upper()
+    tw.write(f"{msg:<8} {scopename} {fixturedef.argname}")
 
     if msg == "SETUP":
         deps = sorted(arg for arg in fixturedef.argnames if arg != "request")
@@ -82,7 +83,7 @@ def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None:
             tw.write(" (fixtures used: {})".format(", ".join(deps)))
 
     if hasattr(fixturedef, "cached_param"):
-        tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]")  # type: ignore[attr-defined]
+        tw.write(f"[{saferepr(fixturedef.cached_param, maxsize=42)}]")
 
     tw.flush()
 
@@ -91,7 +92,7 @@ def _show_fixture_action(fixturedef: FixtureDef[object], msg: str) -> None:
 
 
 @pytest.hookimpl(tryfirst=True)
-def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
+def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
     if config.option.setuponly:
         config.option.setupshow = True
     return None
diff --git a/src/_pytest/setupplan.py b/src/_pytest/setupplan.py
index 9ba81ccaf0a..4e124cce243 100644
--- a/src/_pytest/setupplan.py
+++ b/src/_pytest/setupplan.py
@@ -1,12 +1,11 @@
-from typing import Optional
-from typing import Union
+from __future__ import annotations
 
-import pytest
 from _pytest.config import Config
 from _pytest.config import ExitCode
 from _pytest.config.argparsing import Parser
 from _pytest.fixtures import FixtureDef
 from _pytest.fixtures import SubRequest
+import pytest
 
 
 def pytest_addoption(parser: Parser) -> None:
@@ -15,15 +14,15 @@ def pytest_addoption(parser: Parser) -> None:
         "--setupplan",
         "--setup-plan",
         action="store_true",
-        help="show what fixtures and tests would be executed but "
-        "don't execute anything.",
+        help="Show what fixtures and tests would be executed but "
+        "don't execute anything",
     )
 
 
 @pytest.hookimpl(tryfirst=True)
 def pytest_fixture_setup(
     fixturedef: FixtureDef[object], request: SubRequest
-) -> Optional[object]:
+) -> object | None:
     # Will return a dummy fixture if the setuponly option is provided.
     if request.config.option.setupplan:
         my_cache_key = fixturedef.cache_key(request)
@@ -33,7 +32,7 @@ def pytest_fixture_setup(
 
 
 @pytest.hookimpl(tryfirst=True)
-def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
+def pytest_cmdline_main(config: Config) -> int | ExitCode | None:
     if config.option.setupplan:
         config.option.setuponly = True
         config.option.setupshow = True
diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py
index ac7216f8385..ec118f2c92f 100644
--- a/src/_pytest/skipping.py
+++ b/src/_pytest/skipping.py
@@ -1,15 +1,16 @@
+# mypy: allow-untyped-defs
 """Support for skip/xfail functions and markers."""
+
+from __future__ import annotations
+
+from collections.abc import Generator
+from collections.abc import Mapping
+import dataclasses
 import os
 import platform
 import sys
 import traceback
-from collections.abc import Mapping
-from typing import Generator
 from typing import Optional
-from typing import Tuple
-from typing import Type
-
-import attr
 
 from _pytest.config import Config
 from _pytest.config import hookimpl
@@ -19,7 +20,9 @@
 from _pytest.outcomes import fail
 from _pytest.outcomes import skip
 from _pytest.outcomes import xfail
+from _pytest.raises import AbstractRaises
 from _pytest.reports import BaseReport
+from _pytest.reports import TestReport
 from _pytest.runner import CallInfo
 from _pytest.stash import StashKey
 
@@ -31,12 +34,12 @@ def pytest_addoption(parser: Parser) -> None:
         action="store_true",
         dest="runxfail",
         default=False,
-        help="report the results of xfail tests as if they were not marked",
+        help="Report the results of xfail tests as if they were not marked",
     )
 
     parser.addini(
         "xfail_strict",
-        "default for the strict parameter of xfail "
+        "Default for the strict parameter of xfail "
         "markers when not given explicitly (default: False)",
         default=False,
         type="bool",
@@ -82,7 +85,7 @@ def nop(*args, **kwargs):
     )
 
 
-def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, str]:
+def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, str]:
     """Evaluate a single skipif/xfail condition.
 
     If an old-style string condition is given, it is eval()'d, otherwise the
@@ -104,20 +107,18 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
         ):
             if not isinstance(dictionary, Mapping):
                 raise ValueError(
-                    "pytest_markeval_namespace() needs to return a dict, got {!r}".format(
-                        dictionary
-                    )
+                    f"pytest_markeval_namespace() needs to return a dict, got {dictionary!r}"
                 )
             globals_.update(dictionary)
         if hasattr(item, "obj"):
-            globals_.update(item.obj.__globals__)  # type: ignore[attr-defined]
+            globals_.update(item.obj.__globals__)
         try:
             filename = f"<{mark.name} condition>"
             condition_code = compile(condition, filename, "eval")
             result = eval(condition_code, globals_)
         except SyntaxError as exc:
             msglines = [
-                "Error evaluating %r condition" % mark.name,
+                f"Error evaluating {mark.name!r} condition",
                 "    " + condition,
                 "    " + " " * (exc.offset or 0) + "^",
                 "SyntaxError: invalid syntax",
@@ -125,7 +126,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
             fail("\n".join(msglines), pytrace=False)
         except Exception as exc:
             msglines = [
-                "Error evaluating %r condition" % mark.name,
+                f"Error evaluating {mark.name!r} condition",
                 "    " + condition,
                 *traceback.format_exception_only(type(exc), exc),
             ]
@@ -137,7 +138,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
             result = bool(condition)
         except Exception as exc:
             msglines = [
-                "Error evaluating %r condition as a boolean" % mark.name,
+                f"Error evaluating {mark.name!r} condition as a boolean",
                 *traceback.format_exception_only(type(exc), exc),
             ]
             fail("\n".join(msglines), pytrace=False)
@@ -149,7 +150,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
         else:
             # XXX better be checked at collection time
             msg = (
-                "Error evaluating %r: " % mark.name
+                f"Error evaluating {mark.name!r}: "
                 + "you need to specify reason=STRING when using booleans as conditions."
             )
             fail(msg, pytrace=False)
@@ -157,14 +158,14 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
     return result, reason
 
 
-@attr.s(slots=True, frozen=True, auto_attribs=True)
+@dataclasses.dataclass(frozen=True)
 class Skip:
     """The result of evaluate_skip_marks()."""
 
     reason: str = "unconditional skip"
 
 
-def evaluate_skip_marks(item: Item) -> Optional[Skip]:
+def evaluate_skip_marks(item: Item) -> Skip | None:
     """Evaluate skip and skipif marks on item, returning Skip if triggered."""
     for mark in item.iter_markers(name="skipif"):
         if "condition" not in mark.kwargs:
@@ -192,17 +193,24 @@ def evaluate_skip_marks(item: Item) -> Optional[Skip]:
     return None
 
 
-@attr.s(slots=True, frozen=True, auto_attribs=True)
+@dataclasses.dataclass(frozen=True)
 class Xfail:
     """The result of evaluate_xfail_marks()."""
 
+    __slots__ = ("raises", "reason", "run", "strict")
+
     reason: str
     run: bool
     strict: bool
-    raises: Optional[Tuple[Type[BaseException], ...]]
+    raises: (
+        type[BaseException]
+        | tuple[type[BaseException], ...]
+        | AbstractRaises[BaseException]
+        | None
+    )
 
 
-def evaluate_xfail_marks(item: Item) -> Optional[Xfail]:
+def evaluate_xfail_marks(item: Item) -> Xfail | None:
     """Evaluate xfail marks on item, returning Xfail if triggered."""
     for mark in item.iter_markers(name="xfail"):
         run = mark.kwargs.get("run", True)
@@ -242,8 +250,8 @@ def pytest_runtest_setup(item: Item) -> None:
         xfail("[NOTRUN] " + xfailed.reason)
 
 
-@hookimpl(hookwrapper=True)
-def pytest_runtest_call(item: Item) -> Generator[None, None, None]:
+@hookimpl(wrapper=True)
+def pytest_runtest_call(item: Item) -> Generator[None]:
     xfailed = item.stash.get(xfailed_key, None)
     if xfailed is None:
         item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item)
@@ -251,33 +259,44 @@ def pytest_runtest_call(item: Item) -> Generator[None, None, None]:
     if xfailed and not item.config.option.runxfail and not xfailed.run:
         xfail("[NOTRUN] " + xfailed.reason)
 
-    yield
-
-    # The test run may have added an xfail mark dynamically.
-    xfailed = item.stash.get(xfailed_key, None)
-    if xfailed is None:
-        item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item)
+    try:
+        return (yield)
+    finally:
+        # The test run may have added an xfail mark dynamically.
+        xfailed = item.stash.get(xfailed_key, None)
+        if xfailed is None:
+            item.stash[xfailed_key] = xfailed = evaluate_xfail_marks(item)
 
 
-@hookimpl(hookwrapper=True)
-def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
-    outcome = yield
-    rep = outcome.get_result()
+@hookimpl(wrapper=True)
+def pytest_runtest_makereport(
+    item: Item, call: CallInfo[None]
+) -> Generator[None, TestReport, TestReport]:
+    rep = yield
     xfailed = item.stash.get(xfailed_key, None)
     if item.config.option.runxfail:
         pass  # don't interfere
     elif call.excinfo and isinstance(call.excinfo.value, xfail.Exception):
         assert call.excinfo.value.msg is not None
-        rep.wasxfail = "reason: " + call.excinfo.value.msg
+        rep.wasxfail = call.excinfo.value.msg
         rep.outcome = "skipped"
     elif not rep.skipped and xfailed:
         if call.excinfo:
             raises = xfailed.raises
-            if raises is not None and not isinstance(call.excinfo.value, raises):
-                rep.outcome = "failed"
-            else:
+            if raises is None or (
+                (
+                    isinstance(raises, (type, tuple))
+                    and isinstance(call.excinfo.value, raises)
+                )
+                or (
+                    isinstance(raises, AbstractRaises)
+                    and raises.matches(call.excinfo.value)
+                )
+            ):
                 rep.outcome = "skipped"
                 rep.wasxfail = xfailed.reason
+            else:
+                rep.outcome = "failed"
         elif call.when == "call":
             if xfailed.strict:
                 rep.outcome = "failed"
@@ -285,9 +304,10 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
             else:
                 rep.outcome = "passed"
                 rep.wasxfail = xfailed.reason
+    return rep
 
 
-def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]:
+def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None:
     if hasattr(report, "wasxfail"):
         if report.skipped:
             return "xfailed", "x", "XFAIL"
diff --git a/src/_pytest/stash.py b/src/_pytest/stash.py
index e61d75b95f7..6a9ff884e04 100644
--- a/src/_pytest/stash.py
+++ b/src/_pytest/stash.py
@@ -1,9 +1,9 @@
+from __future__ import annotations
+
 from typing import Any
 from typing import cast
-from typing import Dict
 from typing import Generic
 from typing import TypeVar
-from typing import Union
 
 
 __all__ = ["Stash", "StashKey"]
@@ -19,6 +19,8 @@ class StashKey(Generic[T]):
     A ``StashKey`` is associated with the type ``T`` of the value of the key.
 
     A ``StashKey`` is unique and cannot conflict with another key.
+
+    .. versionadded:: 7.0
     """
 
     __slots__ = ()
@@ -61,12 +63,14 @@ class Stash:
         some_str = stash[some_str_key]
         # The static type of some_bool is bool.
         some_bool = stash[some_bool_key]
+
+    .. versionadded:: 7.0
     """
 
     __slots__ = ("_storage",)
 
     def __init__(self) -> None:
-        self._storage: Dict[StashKey[Any], object] = {}
+        self._storage: dict[StashKey[Any], object] = {}
 
     def __setitem__(self, key: StashKey[T], value: T) -> None:
         """Set a value for key."""
@@ -79,7 +83,7 @@ def __getitem__(self, key: StashKey[T]) -> T:
         """
         return cast(T, self._storage[key])
 
-    def get(self, key: StashKey[T], default: D) -> Union[T, D]:
+    def get(self, key: StashKey[T], default: D) -> T | D:
         """Get the value for key, or return default if the key wasn't set
         before."""
         try:
diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py
index 4d95a96b872..8901540eb59 100644
--- a/src/_pytest/stepwise.py
+++ b/src/_pytest/stepwise.py
@@ -1,16 +1,21 @@
-from typing import List
-from typing import Optional
+from __future__ import annotations
+
+import dataclasses
+from datetime import datetime
+from datetime import timedelta
+from typing import Any
 from typing import TYPE_CHECKING
 
-import pytest
 from _pytest import nodes
+from _pytest.cacheprovider import Cache
 from _pytest.config import Config
 from _pytest.config.argparsing import Parser
 from _pytest.main import Session
 from _pytest.reports import TestReport
 
+
 if TYPE_CHECKING:
-    from _pytest.cacheprovider import Cache
+    from typing_extensions import Self
 
 STEPWISE_CACHE_DIR = "cache/stepwise"
 
@@ -23,7 +28,7 @@ def pytest_addoption(parser: Parser) -> None:
         action="store_true",
         default=False,
         dest="stepwise",
-        help="exit on test failure and continue from last failing test next time",
+        help="Exit on test failure and continue from last failing test next time",
     )
     group.addoption(
         "--sw-skip",
@@ -31,15 +36,23 @@ def pytest_addoption(parser: Parser) -> None:
         action="store_true",
         default=False,
         dest="stepwise_skip",
-        help="ignore the first failing test but stop on the next failing test.\n"
-        "implicitly enables --stepwise.",
+        help="Ignore the first failing test but stop on the next failing test. "
+        "Implicitly enables --stepwise.",
+    )
+    group.addoption(
+        "--sw-reset",
+        "--stepwise-reset",
+        action="store_true",
+        default=False,
+        dest="stepwise_reset",
+        help="Resets stepwise state, restarting the stepwise workflow. "
+        "Implicitly enables --stepwise.",
     )
 
 
-@pytest.hookimpl
 def pytest_configure(config: Config) -> None:
-    if config.option.stepwise_skip:
-        # allow --stepwise-skip to work on it's own merits.
+    # --stepwise-skip/--stepwise-reset implies stepwise.
+    if config.option.stepwise_skip or config.option.stepwise_reset:
         config.option.stepwise = True
     if config.getoption("stepwise"):
         config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin")
@@ -48,43 +61,112 @@ def pytest_configure(config: Config) -> None:
 def pytest_sessionfinish(session: Session) -> None:
     if not session.config.getoption("stepwise"):
         assert session.config.cache is not None
-        # Clear the list of failing tests if the plugin is not active.
-        session.config.cache.set(STEPWISE_CACHE_DIR, [])
+        if hasattr(session.config, "workerinput"):
+            # Do not update cache if this process is a xdist worker to prevent
+            # race conditions (#10641).
+            return
+
+
+@dataclasses.dataclass
+class StepwiseCacheInfo:
+    # The nodeid of the last failed test.
+    last_failed: str | None
+
+    # The number of tests in the last time --stepwise was run.
+    # We use this information as a simple way to invalidate the cache information, avoiding
+    # confusing behavior in case the cache is stale.
+    last_test_count: int | None
+
+    # The date when the cache was last updated, for information purposes only.
+    last_cache_date_str: str
+
+    @property
+    def last_cache_date(self) -> datetime:
+        return datetime.fromisoformat(self.last_cache_date_str)
+
+    @classmethod
+    def empty(cls) -> Self:
+        return cls(
+            last_failed=None,
+            last_test_count=None,
+            last_cache_date_str=datetime.now().isoformat(),
+        )
+
+    def update_date_to_now(self) -> None:
+        self.last_cache_date_str = datetime.now().isoformat()
 
 
 class StepwisePlugin:
     def __init__(self, config: Config) -> None:
         self.config = config
-        self.session: Optional[Session] = None
-        self.report_status = ""
+        self.session: Session | None = None
+        self.report_status: list[str] = []
         assert config.cache is not None
         self.cache: Cache = config.cache
-        self.lastfailed: Optional[str] = self.cache.get(STEPWISE_CACHE_DIR, None)
         self.skip: bool = config.getoption("stepwise_skip")
+        self.reset: bool = config.getoption("stepwise_reset")
+        self.cached_info = self._load_cached_info()
+
+    def _load_cached_info(self) -> StepwiseCacheInfo:
+        cached_dict: dict[str, Any] | None = self.cache.get(STEPWISE_CACHE_DIR, None)
+        if cached_dict:
+            try:
+                return StepwiseCacheInfo(
+                    cached_dict["last_failed"],
+                    cached_dict["last_test_count"],
+                    cached_dict["last_cache_date_str"],
+                )
+            except (KeyError, TypeError) as e:
+                error = f"{type(e).__name__}: {e}"
+                self.report_status.append(f"error reading cache, discarding ({error})")
+
+        # Cache not found or error during load, return a new cache.
+        return StepwiseCacheInfo.empty()
 
     def pytest_sessionstart(self, session: Session) -> None:
         self.session = session
 
     def pytest_collection_modifyitems(
-        self, config: Config, items: List[nodes.Item]
+        self, config: Config, items: list[nodes.Item]
     ) -> None:
-        if not self.lastfailed:
-            self.report_status = "no previously failed tests, not skipping."
+        last_test_count = self.cached_info.last_test_count
+        self.cached_info.last_test_count = len(items)
+
+        if self.reset:
+            self.report_status.append("resetting state, not skipping.")
+            self.cached_info.last_failed = None
             return
 
-        # check all item nodes until we find a match on last failed
+        if not self.cached_info.last_failed:
+            self.report_status.append("no previously failed tests, not skipping.")
+            return
+
+        if last_test_count is not None and last_test_count != len(items):
+            self.report_status.append(
+                f"test count changed, not skipping (now {len(items)} tests, previously {last_test_count})."
+            )
+            self.cached_info.last_failed = None
+            return
+
+        # Check all item nodes until we find a match on last failed.
         failed_index = None
         for index, item in enumerate(items):
-            if item.nodeid == self.lastfailed:
+            if item.nodeid == self.cached_info.last_failed:
                 failed_index = index
                 break
 
         # If the previously failed test was not found among the test items,
         # do not skip any tests.
         if failed_index is None:
-            self.report_status = "previously failed test not found, not skipping."
+            self.report_status.append("previously failed test not found, not skipping.")
         else:
-            self.report_status = f"skipping {failed_index} already passed items."
+            cache_age = datetime.now() - self.cached_info.last_cache_date
+            # Round up to avoid showing microseconds.
+            cache_age = timedelta(seconds=int(cache_age.total_seconds()))
+            self.report_status.append(
+                f"skipping {failed_index} already passed items (cache from {cache_age} ago,"
+                f" use --sw-reset to discard)."
+            )
             deselected = items[:failed_index]
             del items[:failed_index]
             config.hook.pytest_deselected(items=deselected)
@@ -94,13 +176,13 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
             if self.skip:
                 # Remove test from the failed ones (if it exists) and unset the skip option
                 # to make sure the following tests will not be skipped.
-                if report.nodeid == self.lastfailed:
-                    self.lastfailed = None
+                if report.nodeid == self.cached_info.last_failed:
+                    self.cached_info.last_failed = None
 
                 self.skip = False
             else:
                 # Mark test as the last failing and interrupt the test session.
-                self.lastfailed = report.nodeid
+                self.cached_info.last_failed = report.nodeid
                 assert self.session is not None
                 self.session.shouldstop = (
                     "Test failed, continuing from this test next run."
@@ -110,13 +192,18 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
             # If the test was actually run and did pass.
             if report.when == "call":
                 # Remove test from the failed ones, if exists.
-                if report.nodeid == self.lastfailed:
-                    self.lastfailed = None
+                if report.nodeid == self.cached_info.last_failed:
+                    self.cached_info.last_failed = None
 
-    def pytest_report_collectionfinish(self) -> Optional[str]:
-        if self.config.getoption("verbose") >= 0 and self.report_status:
-            return f"stepwise: {self.report_status}"
+    def pytest_report_collectionfinish(self) -> list[str] | None:
+        if self.config.get_verbosity() >= 0 and self.report_status:
+            return [f"stepwise: {x}" for x in self.report_status]
         return None
 
     def pytest_sessionfinish(self) -> None:
-        self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed)
+        if hasattr(self.config, "workerinput"):
+            # Do not update cache if this process is a xdist worker to prevent
+            # race conditions (#10641).
+            return
+        self.cached_info.update_date_to_now()
+        self.cache.set(STEPWISE_CACHE_DIR, dataclasses.asdict(self.cached_info))
diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py
index ccbd84d7d71..3297c38e848 100644
--- a/src/_pytest/terminal.py
+++ b/src/_pytest/terminal.py
@@ -1,42 +1,44 @@
+# mypy: allow-untyped-defs
 """Terminal reporting of the full testing process.
 
 This is a good source for looking at the various reporting hooks.
 """
+
+from __future__ import annotations
+
 import argparse
+from collections import Counter
+from collections.abc import Callable
+from collections.abc import Generator
+from collections.abc import Mapping
+from collections.abc import Sequence
+import dataclasses
 import datetime
+from functools import partial
 import inspect
+from pathlib import Path
 import platform
 import sys
-import warnings
-from collections import Counter
-from functools import partial
-from pathlib import Path
+import textwrap
 from typing import Any
-from typing import Callable
-from typing import cast
 from typing import ClassVar
-from typing import Dict
-from typing import Generator
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Sequence
-from typing import Set
+from typing import final
+from typing import Literal
+from typing import NamedTuple
 from typing import TextIO
-from typing import Tuple
 from typing import TYPE_CHECKING
-from typing import Union
+import warnings
 
-import attr
 import pluggy
 
-import _pytest._version
 from _pytest import nodes
 from _pytest import timing
 from _pytest._code import ExceptionInfo
 from _pytest._code.code import ExceptionRepr
+from _pytest._io import TerminalWriter
 from _pytest._io.wcwidth import wcswidth
-from _pytest.compat import final
+import _pytest._version
+from _pytest.assertion.util import running_on_ci
 from _pytest.config import _PluggyPlugin
 from _pytest.config import Config
 from _pytest.config import ExitCode
@@ -50,9 +52,8 @@
 from _pytest.reports import CollectReport
 from _pytest.reports import TestReport
 
-if TYPE_CHECKING:
-    from typing_extensions import Literal
 
+if TYPE_CHECKING:
     from _pytest.main import Session
 
 
@@ -85,7 +86,7 @@ def __init__(
         dest: str,
         default: object = None,
         required: bool = False,
-        help: Optional[str] = None,
+        help: str | None = None,
     ) -> None:
         super().__init__(
             option_strings=option_strings,
@@ -100,8 +101,8 @@ def __call__(
         self,
         parser: argparse.ArgumentParser,
         namespace: argparse.Namespace,
-        values: Union[str, Sequence[object], None],
-        option_string: Optional[str] = None,
+        values: str | Sequence[object] | None,
+        option_string: str | None = None,
     ) -> None:
         new_count = getattr(namespace, self.dest, 0) - 1
         setattr(namespace, self.dest, new_count)
@@ -109,119 +110,177 @@ def __call__(
         namespace.quiet = getattr(namespace, "quiet", 0) + 1
 
 
+class TestShortLogReport(NamedTuple):
+    """Used to store the test status result category, shortletter and verbose word.
+    For example ``"rerun", "R", ("RERUN", {"yellow": True})``.
+
+    :ivar category:
+        The class of result, for example ``“passed”``, ``“skipped”``, ``“error”``, or the empty string.
+
+    :ivar letter:
+        The short letter shown as testing progresses, for example ``"."``, ``"s"``, ``"E"``, or the empty string.
+
+    :ivar word:
+        Verbose word is shown as testing progresses in verbose mode, for example ``"PASSED"``, ``"SKIPPED"``,
+        ``"ERROR"``, or the empty string.
+    """
+
+    category: str
+    letter: str
+    word: str | tuple[str, Mapping[str, bool]]
+
+
 def pytest_addoption(parser: Parser) -> None:
-    group = parser.getgroup("terminal reporting", "reporting", after="general")
-    group._addoption(
+    group = parser.getgroup("terminal reporting", "Reporting", after="general")
+    group._addoption(  # private to use reserved lower-case short option
         "-v",
         "--verbose",
         action="count",
         default=0,
         dest="verbose",
-        help="increase verbosity.",
+        help="Increase verbosity",
     )
-    group._addoption(
+    group.addoption(
         "--no-header",
         action="store_true",
         default=False,
         dest="no_header",
-        help="disable header",
+        help="Disable header",
     )
-    group._addoption(
+    group.addoption(
         "--no-summary",
         action="store_true",
         default=False,
         dest="no_summary",
-        help="disable summary",
+        help="Disable summary",
     )
-    group._addoption(
+    group.addoption(
+        "--no-fold-skipped",
+        action="store_false",
+        dest="fold_skipped",
+        default=True,
+        help="Do not fold skipped tests in short summary.",
+    )
+    group.addoption(
+        "--force-short-summary",
+        action="store_true",
+        dest="force_short_summary",
+        default=False,
+        help="Force condensed summary output regardless of verbosity level.",
+    )
+    group._addoption(  # private to use reserved lower-case short option
         "-q",
         "--quiet",
         action=MoreQuietAction,
         default=0,
         dest="verbose",
-        help="decrease verbosity.",
+        help="Decrease verbosity",
     )
-    group._addoption(
+    group.addoption(
         "--verbosity",
         dest="verbose",
         type=int,
         default=0,
-        help="set verbosity. Default is 0.",
+        help="Set verbosity. Default: 0.",
     )
-    group._addoption(
+    group._addoption(  # private to use reserved lower-case short option
         "-r",
         action="store",
         dest="reportchars",
         default=_REPORTCHARS_DEFAULT,
         metavar="chars",
-        help="show extra test summary info as specified by chars: (f)ailed, "
+        help="Show extra test summary info as specified by chars: (f)ailed, "
         "(E)rror, (s)kipped, (x)failed, (X)passed, "
         "(p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. "
         "(w)arnings are enabled by default (see --disable-warnings), "
         "'N' can be used to reset the list. (default: 'fE').",
     )
-    group._addoption(
+    group.addoption(
         "--disable-warnings",
         "--disable-pytest-warnings",
         default=False,
         dest="disable_warnings",
         action="store_true",
-        help="disable warnings summary",
+        help="Disable warnings summary",
     )
-    group._addoption(
+    group._addoption(  # private to use reserved lower-case short option
         "-l",
         "--showlocals",
         action="store_true",
         dest="showlocals",
         default=False,
-        help="show locals in tracebacks (disabled by default).",
+        help="Show locals in tracebacks (disabled by default)",
     )
-    group._addoption(
+    group.addoption(
+        "--no-showlocals",
+        action="store_false",
+        dest="showlocals",
+        help="Hide locals in tracebacks (negate --showlocals passed through addopts)",
+    )
+    group.addoption(
         "--tb",
         metavar="style",
         action="store",
         dest="tbstyle",
         default="auto",
         choices=["auto", "long", "short", "no", "line", "native"],
-        help="traceback print mode (auto/long/short/line/native/no).",
+        help="Traceback print mode (auto/long/short/line/native/no)",
     )
-    group._addoption(
+    group.addoption(
+        "--xfail-tb",
+        action="store_true",
+        dest="xfail_tb",
+        default=False,
+        help="Show tracebacks for xfail (as long as --tb != no)",
+    )
+    group.addoption(
         "--show-capture",
         action="store",
         dest="showcapture",
         choices=["no", "stdout", "stderr", "log", "all"],
         default="all",
         help="Controls how captured stdout/stderr/log is shown on failed tests. "
-        "Default is 'all'.",
+        "Default: all.",
     )
-    group._addoption(
+    group.addoption(
         "--fulltrace",
         "--full-trace",
         action="store_true",
         default=False,
-        help="don't cut any tracebacks (default is to cut).",
+        help="Don't cut any tracebacks (default is to cut)",
     )
-    group._addoption(
+    group.addoption(
         "--color",
         metavar="color",
         action="store",
         dest="color",
         default="auto",
         choices=["yes", "no", "auto"],
-        help="color terminal output (yes/no/auto).",
+        help="Color terminal output (yes/no/auto)",
     )
-    group._addoption(
+    group.addoption(
         "--code-highlight",
         default="yes",
         choices=["yes", "no"],
-        help="Whether code should be highlighted (only if --color is also enabled)",
+        help="Whether code should be highlighted (only if --color is also enabled). "
+        "Default: yes.",
     )
 
     parser.addini(
         "console_output_style",
-        help='console output: "classic", or with additional progress information ("progress" (percentage) | "count").',
+        help='Console output: "classic", or with additional progress information '
+        '("progress" (percentage) | "count" | "progress-even-when-capture-no" (forces '
+        "progress even when capture=no)",
         default="progress",
     )
+    Config._add_verbosity_ini(
+        parser,
+        Config.VERBOSITY_TEST_CASES,
+        help=(
+            "Specify a verbosity level for test case execution, overriding the main level. "
+            "Higher levels will provide more detailed information about each test case executed."
+        ),
+    )
 
 
 def pytest_configure(config: Config) -> None:
@@ -262,7 +321,7 @@ def getreportopt(config: Config) -> str:
 
 
 @hookimpl(trylast=True)  # after _pytest.runner
-def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]:
+def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str]:
     letter = "F"
     if report.passed:
         letter = "."
@@ -277,7 +336,7 @@ def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]:
     return outcome, letter, outcome.upper()
 
 
-@attr.s(auto_attribs=True)
+@dataclasses.dataclass
 class WarningReport:
     """Simple structure to hold warnings information captured by ``pytest_warning_recorded``.
 
@@ -290,12 +349,12 @@ class WarningReport:
     """
 
     message: str
-    nodeid: Optional[str] = None
-    fslocation: Optional[Tuple[str, int]] = None
+    nodeid: str | None = None
+    fslocation: tuple[str, int] | None = None
 
     count_towards_summary: ClassVar = True
 
-    def get_location(self, config: Config) -> Optional[str]:
+    def get_location(self, config: Config) -> str | None:
         """Return the more user-friendly information about the location of a warning, or None."""
         if self.nodeid:
             return self.nodeid
@@ -308,45 +367,56 @@ def get_location(self, config: Config) -> Optional[str]:
 
 @final
 class TerminalReporter:
-    def __init__(self, config: Config, file: Optional[TextIO] = None) -> None:
+    def __init__(self, config: Config, file: TextIO | None = None) -> None:
         import _pytest.config
 
         self.config = config
         self._numcollected = 0
-        self._session: Optional[Session] = None
-        self._showfspath: Optional[bool] = None
+        self._session: Session | None = None
+        self._showfspath: bool | None = None
 
-        self.stats: Dict[str, List[Any]] = {}
-        self._main_color: Optional[str] = None
-        self._known_types: Optional[List[str]] = None
+        self.stats: dict[str, list[Any]] = {}
+        self._main_color: str | None = None
+        self._known_types: list[str] | None = None
         self.startpath = config.invocation_params.dir
         if file is None:
             file = sys.stdout
         self._tw = _pytest.config.create_terminal_writer(config, file)
         self._screen_width = self._tw.fullwidth
-        self.currentfspath: Union[None, Path, str, int] = None
+        self.currentfspath: None | Path | str | int = None
         self.reportchars = getreportopt(config)
+        self.foldskipped = config.option.fold_skipped
         self.hasmarkup = self._tw.hasmarkup
         self.isatty = file.isatty()
-        self._progress_nodeids_reported: Set[str] = set()
+        self._progress_nodeids_reported: set[str] = set()
+        self._timing_nodeids_reported: set[str] = set()
         self._show_progress_info = self._determine_show_progress_info()
-        self._collect_report_last_write: Optional[float] = None
-        self._already_displayed_warnings: Optional[int] = None
-        self._keyboardinterrupt_memo: Optional[ExceptionRepr] = None
+        self._collect_report_last_write: float | None = None
+        self._already_displayed_warnings: int | None = None
+        self._keyboardinterrupt_memo: ExceptionRepr | None = None
 
-    def _determine_show_progress_info(self) -> "Literal['progress', 'count', False]":
+    def _determine_show_progress_info(
+        self,
+    ) -> Literal["progress", "count", "times", False]:
         """Return whether we should display progress information based on the current config."""
-        # do not show progress if we are not capturing output (#3038)
-        if self.config.getoption("capture", "no") == "no":
+        # do not show progress if we are not capturing output (#3038) unless explicitly
+        # overridden by progress-even-when-capture-no
+        if (
+            self.config.getoption("capture", "no") == "no"
+            and self.config.getini("console_output_style")
+            != "progress-even-when-capture-no"
+        ):
             return False
         # do not show progress if we are showing fixture setup/teardown
         if self.config.getoption("setupshow", False):
             return False
         cfg: str = self.config.getini("console_output_style")
-        if cfg == "progress":
+        if cfg in {"progress", "progress-even-when-capture-no"}:
             return "progress"
         elif cfg == "count":
             return "count"
+        elif cfg == "times":
+            return "times"
         else:
             return False
 
@@ -370,22 +440,22 @@ def no_summary(self) -> bool:
     @property
     def showfspath(self) -> bool:
         if self._showfspath is None:
-            return self.verbosity >= 0
+            return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) >= 0
         return self._showfspath
 
     @showfspath.setter
-    def showfspath(self, value: Optional[bool]) -> None:
+    def showfspath(self, value: bool | None) -> None:
         self._showfspath = value
 
     @property
     def showlongtestinfo(self) -> bool:
-        return self.verbosity > 0
+        return self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) > 0
 
     def hasopt(self, char: str) -> bool:
         char = {"xfailed": "x", "skipped": "s"}.get(char, char)
         return char in self.reportchars
 
-    def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None:
+    def write_fspath_result(self, nodeid: str, res: str, **markup: bool) -> None:
         fspath = self.config.rootpath / nodeid.split("::")[0]
         if self.currentfspath is None or fspath != self.currentfspath:
             if self.currentfspath is not None and self._show_progress_info:
@@ -410,13 +480,35 @@ def ensure_newline(self) -> None:
             self._tw.line()
             self.currentfspath = None
 
+    def wrap_write(
+        self,
+        content: str,
+        *,
+        flush: bool = False,
+        margin: int = 8,
+        line_sep: str = "\n",
+        **markup: bool,
+    ) -> None:
+        """Wrap message with margin for progress info."""
+        width_of_current_line = self._tw.width_of_current_line
+        wrapped = line_sep.join(
+            textwrap.wrap(
+                " " * width_of_current_line + content,
+                width=self._screen_width - margin,
+                drop_whitespace=True,
+                replace_whitespace=False,
+            ),
+        )
+        wrapped = wrapped[width_of_current_line:]
+        self._tw.write(wrapped, flush=flush, **markup)
+
     def write(self, content: str, *, flush: bool = False, **markup: bool) -> None:
         self._tw.write(content, flush=flush, **markup)
 
     def flush(self) -> None:
         self._tw.flush()
 
-    def write_line(self, line: Union[str, bytes], **markup: bool) -> None:
+    def write_line(self, line: str | bytes, **markup: bool) -> None:
         if not isinstance(line, str):
             line = str(line, errors="replace")
         self.ensure_newline()
@@ -443,8 +535,8 @@ def rewrite(self, line: str, **markup: bool) -> None:
     def write_sep(
         self,
         sep: str,
-        title: Optional[str] = None,
-        fullwidth: Optional[int] = None,
+        title: str | None = None,
+        fullwidth: int | None = None,
         **markup: bool,
     ) -> None:
         self.ensure_newline()
@@ -494,12 +586,13 @@ def pytest_deselected(self, items: Sequence[Item]) -> None:
         self._add_stats("deselected", items)
 
     def pytest_runtest_logstart(
-        self, nodeid: str, location: Tuple[str, Optional[int], str]
+        self, nodeid: str, location: tuple[str, int | None, str]
     ) -> None:
+        fspath, lineno, domain = location
         # Ensure that the path is printed before the
         # 1st test of a module starts running.
         if self.showlongtestinfo:
-            line = self._locationline(nodeid, *location)
+            line = self._locationline(nodeid, fspath, lineno, domain)
             self.write_ensure_prefix(line, "")
             self.flush()
         elif self.showfspath:
@@ -509,10 +602,11 @@ def pytest_runtest_logstart(
     def pytest_runtest_logreport(self, report: TestReport) -> None:
         self._tests_ran = True
         rep = report
-        res: Tuple[
-            str, str, Union[str, Tuple[str, Mapping[str, bool]]]
-        ] = self.config.hook.pytest_report_teststatus(report=rep, config=self.config)
-        category, letter, word = res
+
+        res = TestShortLogReport(
+            *self.config.hook.pytest_report_teststatus(report=rep, config=self.config)
+        )
+        category, letter, word = res.category, res.letter, res.word
         if not isinstance(word, tuple):
             markup = None
         else:
@@ -521,7 +615,6 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
         if not letter and not word:
             # Probably passed setup/teardown.
             return
-        running_xdist = hasattr(rep, "node")
         if markup is None:
             was_xfail = hasattr(report, "wasxfail")
             if rep.passed and not was_xfail:
@@ -534,28 +627,43 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
                 markup = {"yellow": True}
             else:
                 markup = {}
-        if self.verbosity <= 0:
+        self._progress_nodeids_reported.add(rep.nodeid)
+        if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0:
             self._tw.write(letter, **markup)
+            # When running in xdist, the logreport and logfinish of multiple
+            # items are interspersed, e.g. `logreport`, `logreport`,
+            # `logfinish`, `logfinish`. To avoid the "past edge" calculation
+            # from getting confused and overflowing (#7166), do the past edge
+            # printing here and not in logfinish, except for the 100% which
+            # should only be printed after all teardowns are finished.
+            if self._show_progress_info and not self._is_last_item:
+                self._write_progress_information_if_past_edge()
         else:
-            self._progress_nodeids_reported.add(rep.nodeid)
             line = self._locationline(rep.nodeid, *rep.location)
+            running_xdist = hasattr(rep, "node")
             if not running_xdist:
                 self.write_ensure_prefix(line, word, **markup)
                 if rep.skipped or hasattr(report, "wasxfail"):
-                    available_width = (
-                        (self._tw.fullwidth - self._tw.width_of_current_line)
-                        - len(" [100%]")
-                        - 1
-                    )
                     reason = _get_raw_skip_reason(rep)
-                    reason_ = _format_trimmed(" ({})", reason, available_width)
-                    if reason and reason_ is not None:
-                        self._tw.write(reason_)
+                    if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) < 2:
+                        available_width = (
+                            (self._tw.fullwidth - self._tw.width_of_current_line)
+                            - len(" [100%]")
+                            - 1
+                        )
+                        formatted_reason = _format_trimmed(
+                            " ({})", reason, available_width
+                        )
+                    else:
+                        formatted_reason = f" ({reason})"
+
+                    if reason and formatted_reason is not None:
+                        self.wrap_write(formatted_reason)
                 if self._show_progress_info:
                     self._write_progress_information_filling_space()
             else:
                 self.ensure_newline()
-                self._tw.write("[%s]" % rep.node.gateway.id)
+                self._tw.write(f"[{rep.node.gateway.id}]")
                 if self._show_progress_info:
                     self._tw.write(
                         self._get_progress_information_message() + " ", cyan=True
@@ -572,43 +680,78 @@ def _is_last_item(self) -> bool:
         assert self._session is not None
         return len(self._progress_nodeids_reported) == self._session.testscollected
 
-    def pytest_runtest_logfinish(self, nodeid: str) -> None:
-        assert self._session
-        if self.verbosity <= 0 and self._show_progress_info:
-            if self._show_progress_info == "count":
-                num_tests = self._session.testscollected
-                progress_length = len(f" [{num_tests}/{num_tests}]")
-            else:
-                progress_length = len(" [100%]")
+    @hookimpl(wrapper=True)
+    def pytest_runtestloop(self) -> Generator[None, object, object]:
+        result = yield
 
-            self._progress_nodeids_reported.add(nodeid)
+        # Write the final/100% progress -- deferred until the loop is complete.
+        if (
+            self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0
+            and self._show_progress_info
+            and self._progress_nodeids_reported
+        ):
+            self._write_progress_information_filling_space()
 
-            if self._is_last_item:
-                self._write_progress_information_filling_space()
-            else:
-                main_color, _ = self._get_main_color()
-                w = self._width_of_current_line
-                past_edge = w + progress_length + 1 >= self._screen_width
-                if past_edge:
-                    msg = self._get_progress_information_message()
-                    self._tw.write(msg + "\n", **{main_color: True})
+        return result
 
     def _get_progress_information_message(self) -> str:
         assert self._session
         collected = self._session.testscollected
         if self._show_progress_info == "count":
             if collected:
-                progress = self._progress_nodeids_reported
+                progress = len(self._progress_nodeids_reported)
                 counter_format = f"{{:{len(str(collected))}d}}"
                 format_string = f" [{counter_format}/{{}}]"
-                return format_string.format(len(progress), collected)
+                return format_string.format(progress, collected)
             return f" [ {collected} / {collected} ]"
+        if self._show_progress_info == "times":
+            if not collected:
+                return ""
+            all_reports = (
+                self._get_reports_to_display("passed")
+                + self._get_reports_to_display("xpassed")
+                + self._get_reports_to_display("failed")
+                + self._get_reports_to_display("xfailed")
+                + self._get_reports_to_display("skipped")
+                + self._get_reports_to_display("error")
+                + self._get_reports_to_display("")
+            )
+            current_location = all_reports[-1].location[0]
+            not_reported = [
+                r for r in all_reports if r.nodeid not in self._timing_nodeids_reported
+            ]
+            tests_in_module = sum(
+                i.location[0] == current_location for i in self._session.items
+            )
+            tests_completed = sum(
+                r.when == "setup"
+                for r in not_reported
+                if r.location[0] == current_location
+            )
+            last_in_module = tests_completed == tests_in_module
+            if self.showlongtestinfo or last_in_module:
+                self._timing_nodeids_reported.update(r.nodeid for r in not_reported)
+                return format_node_duration(sum(r.duration for r in not_reported))
+            return ""
+        if collected:
+            return f" [{len(self._progress_nodeids_reported) * 100 // collected:3d}%]"
+        return " [100%]"
+
+    def _write_progress_information_if_past_edge(self) -> None:
+        w = self._width_of_current_line
+        if self._show_progress_info == "count":
+            assert self._session
+            num_tests = self._session.testscollected
+            progress_length = len(f" [{num_tests}/{num_tests}]")
+        elif self._show_progress_info == "times":
+            progress_length = len(" 99h 59m")
         else:
-            if collected:
-                return " [{:3d}%]".format(
-                    len(self._progress_nodeids_reported) * 100 // collected
-                )
-            return " [100%]"
+            progress_length = len(" [100%]")
+        past_edge = w + progress_length + 1 >= self._screen_width
+        if past_edge:
+            main_color, _ = self._get_main_color()
+            msg = self._get_progress_information_message()
+            self._tw.write(msg + "\n", **{main_color: True})
 
     def _write_progress_information_filling_space(self) -> None:
         color, _ = self._get_main_color()
@@ -657,19 +800,19 @@ def report_collect(self, final: bool = False) -> None:
         errors = len(self.stats.get("error", []))
         skipped = len(self.stats.get("skipped", []))
         deselected = len(self.stats.get("deselected", []))
-        selected = self._numcollected - errors - skipped - deselected
+        selected = self._numcollected - deselected
         line = "collected " if final else "collecting "
         line += (
             str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s")
         )
         if errors:
-            line += " / %d error%s" % (errors, "s" if errors != 1 else "")
+            line += f" / {errors} error{'s' if errors != 1 else ''}"
         if deselected:
-            line += " / %d deselected" % deselected
+            line += f" / {deselected} deselected"
         if skipped:
-            line += " / %d skipped" % skipped
-        if self._numcollected > selected > 0:
-            line += " / %d selected" % selected
+            line += f" / {skipped} skipped"
+        if self._numcollected > selected:
+            line += f" / {selected} selected"
         if self.isatty:
             self.rewrite(line, bold=True, erase=True)
             if final:
@@ -678,7 +821,7 @@ def report_collect(self, final: bool = False) -> None:
             self.write_line(line)
 
     @hookimpl(trylast=True)
-    def pytest_sessionstart(self, session: "Session") -> None:
+    def pytest_sessionstart(self, session: Session) -> None:
         self._session = session
         self._sessionstarttime = timing.time()
         if not self.showheader:
@@ -691,9 +834,7 @@ def pytest_sessionstart(self, session: "Session") -> None:
             if pypy_version_info:
                 verinfo = ".".join(map(str, pypy_version_info[:3]))
                 msg += f"[pypy-{verinfo}-{pypy_version_info[3]}]"
-            msg += ", pytest-{}, pluggy-{}".format(
-                _pytest._version.version, pluggy.__version__
-            )
+            msg += f", pytest-{_pytest._version.version}, pluggy-{pluggy.__version__}"
             if (
                 self.verbosity > 0
                 or self.config.option.debug
@@ -707,7 +848,7 @@ def pytest_sessionstart(self, session: "Session") -> None:
             self._write_report_lines_from_hooks(lines)
 
     def _write_report_lines_from_hooks(
-        self, lines: Sequence[Union[str, Sequence[str]]]
+        self, lines: Sequence[str | Sequence[str]]
     ) -> None:
         for line_or_lines in reversed(lines):
             if isinstance(line_or_lines, str):
@@ -716,24 +857,24 @@ def _write_report_lines_from_hooks(
                 for line in line_or_lines:
                     self.write_line(line)
 
-    def pytest_report_header(self, config: Config) -> List[str]:
-        line = "rootdir: %s" % config.rootpath
+    def pytest_report_header(self, config: Config) -> list[str]:
+        result = [f"rootdir: {config.rootpath}"]
 
         if config.inipath:
-            line += ", configfile: " + bestrelpath(config.rootpath, config.inipath)
+            result.append("configfile: " + bestrelpath(config.rootpath, config.inipath))
 
-        testpaths: List[str] = config.getini("testpaths")
-        if config.invocation_params.dir == config.rootpath and config.args == testpaths:
-            line += ", testpaths: {}".format(", ".join(testpaths))
-
-        result = [line]
+        if config.args_source == Config.ArgsSource.TESTPATHS:
+            testpaths: list[str] = config.getini("testpaths")
+            result.append("testpaths: {}".format(", ".join(testpaths)))
 
         plugininfo = config.pluginmanager.list_plugin_distinfo()
         if plugininfo:
-            result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
+            result.append(
+                "plugins: {}".format(", ".join(_plugin_nameversions(plugininfo)))
+            )
         return result
 
-    def pytest_collection_finish(self, session: "Session") -> None:
+    def pytest_collection_finish(self, session: Session) -> None:
         self.report_collect(True)
 
         lines = self.config.hook.pytest_report_collectionfinish(
@@ -756,16 +897,17 @@ def pytest_collection_finish(self, session: "Session") -> None:
                     rep.toterminal(self._tw)
 
     def _printcollecteditems(self, items: Sequence[Item]) -> None:
-        if self.config.option.verbose < 0:
-            if self.config.option.verbose < -1:
+        test_cases_verbosity = self.config.get_verbosity(Config.VERBOSITY_TEST_CASES)
+        if test_cases_verbosity < 0:
+            if test_cases_verbosity < -1:
                 counts = Counter(item.nodeid.split("::", 1)[0] for item in items)
                 for name, count in sorted(counts.items()):
-                    self._tw.line("%s: %d" % (name, count))
+                    self._tw.line(f"{name}: {count}")
             else:
                 for item in items:
                     self._tw.line(item.nodeid)
             return
-        stack: List[Node] = []
+        stack: list[Node] = []
         indent = ""
         for item in items:
             needed_collectors = item.listchain()[1:]  # strip root node
@@ -777,19 +919,18 @@ def _printcollecteditems(self, items: Sequence[Item]) -> None:
                 stack.append(col)
                 indent = (len(stack) - 1) * "  "
                 self._tw.line(f"{indent}{col}")
-                if self.config.option.verbose >= 1:
+                if test_cases_verbosity >= 1:
                     obj = getattr(col, "obj", None)
                     doc = inspect.getdoc(obj) if obj else None
                     if doc:
                         for line in doc.splitlines():
                             self._tw.line("{}{}".format(indent + "  ", line))
 
-    @hookimpl(hookwrapper=True)
+    @hookimpl(wrapper=True)
     def pytest_sessionfinish(
-        self, session: "Session", exitstatus: Union[int, ExitCode]
-    ):
-        outcome = yield
-        outcome.get_result()
+        self, session: Session, exitstatus: int | ExitCode
+    ) -> Generator[None]:
+        result = yield
         self._tw.line("")
         summary_exit_codes = (
             ExitCode.OK,
@@ -810,17 +951,22 @@ def pytest_sessionfinish(
         elif session.shouldstop:
             self.write_sep("!", str(session.shouldstop), red=True)
         self.summary_stats()
+        return result
 
-    @hookimpl(hookwrapper=True)
-    def pytest_terminal_summary(self) -> Generator[None, None, None]:
+    @hookimpl(wrapper=True)
+    def pytest_terminal_summary(self) -> Generator[None]:
         self.summary_errors()
         self.summary_failures()
+        self.summary_xfailures()
         self.summary_warnings()
         self.summary_passes()
-        yield
-        self.short_test_summary()
-        # Display any extra warnings from teardown here (if any).
-        self.summary_warnings()
+        self.summary_xpasses()
+        try:
+            return (yield)
+        finally:
+            self.short_test_summary()
+            # Display any extra warnings from teardown here (if any).
+            self.summary_warnings()
 
     def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None:
         self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
@@ -846,7 +992,7 @@ def _report_keyboardinterrupt(self) -> None:
                 )
 
     def _locationline(
-        self, nodeid: str, fspath: str, lineno: Optional[int], domain: str
+        self, nodeid: str, fspath: str, lineno: int | None, domain: str
     ) -> str:
         def mkrel(nodeid: str) -> str:
             line = self.config.cwd_relative_nodeid(nodeid)
@@ -857,7 +1003,7 @@ def mkrel(nodeid: str) -> str:
                 line += "[".join(values)
             return line
 
-        # collect_fspath comes from testid which has a "/"-normalized path.
+        # fspath comes from testid which has a "/"-normalized path.
         if fspath:
             res = mkrel(nodeid)
             if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
@@ -891,7 +1037,7 @@ def getreports(self, name: str):
 
     def summary_warnings(self) -> None:
         if self.hasopt("w"):
-            all_warnings: Optional[List[WarningReport]] = self.stats.get("warnings")
+            all_warnings: list[WarningReport] | None = self.stats.get("warnings")
             if not all_warnings:
                 return
 
@@ -904,11 +1050,11 @@ def summary_warnings(self) -> None:
             if not warning_reports:
                 return
 
-            reports_grouped_by_message: Dict[str, List[WarningReport]] = {}
+            reports_grouped_by_message: dict[str, list[WarningReport]] = {}
             for wr in warning_reports:
                 reports_grouped_by_message.setdefault(wr.message, []).append(wr)
 
-            def collapsed_location_report(reports: List[WarningReport]) -> str:
+            def collapsed_location_report(reports: list[WarningReport]) -> str:
                 locations = []
                 for w in reports:
                     location = w.get_location(self.config)
@@ -944,12 +1090,20 @@ def collapsed_location_report(reports: List[WarningReport]) -> str:
             )
 
     def summary_passes(self) -> None:
+        self.summary_passes_combined("passed", "PASSES", "P")
+
+    def summary_xpasses(self) -> None:
+        self.summary_passes_combined("xpassed", "XPASSES", "X")
+
+    def summary_passes_combined(
+        self, which_reports: str, sep_title: str, needed_opt: str
+    ) -> None:
         if self.config.option.tbstyle != "no":
-            if self.hasopt("P"):
-                reports: List[TestReport] = self.getreports("passed")
+            if self.hasopt(needed_opt):
+                reports: list[TestReport] = self.getreports(which_reports)
                 if not reports:
                     return
-                self.write_sep("=", "PASSES")
+                self.write_sep("=", sep_title)
                 for rep in reports:
                     if rep.sections:
                         msg = self._getfailureheadline(rep)
@@ -957,7 +1111,7 @@ def summary_passes(self) -> None:
                         self._outrep_summary(rep)
                     self._handle_teardown_sections(rep.nodeid)
 
-    def _get_teardown_reports(self, nodeid: str) -> List[TestReport]:
+    def _get_teardown_reports(self, nodeid: str) -> list[TestReport]:
         reports = self.getreports("")
         return [
             report
@@ -983,25 +1137,42 @@ def print_teardown_sections(self, rep: TestReport) -> None:
                 self._tw.line(content)
 
     def summary_failures(self) -> None:
-        if self.config.option.tbstyle != "no":
-            reports: List[BaseReport] = self.getreports("failed")
-            if not reports:
-                return
-            self.write_sep("=", "FAILURES")
-            if self.config.option.tbstyle == "line":
-                for rep in reports:
-                    line = self._getcrashline(rep)
-                    self.write_line(line)
-            else:
-                for rep in reports:
-                    msg = self._getfailureheadline(rep)
-                    self.write_sep("_", msg, red=True, bold=True)
-                    self._outrep_summary(rep)
-                    self._handle_teardown_sections(rep.nodeid)
+        style = self.config.option.tbstyle
+        self.summary_failures_combined("failed", "FAILURES", style=style)
+
+    def summary_xfailures(self) -> None:
+        show_tb = self.config.option.xfail_tb
+        style = self.config.option.tbstyle if show_tb else "no"
+        self.summary_failures_combined("xfailed", "XFAILURES", style=style)
+
+    def summary_failures_combined(
+        self,
+        which_reports: str,
+        sep_title: str,
+        *,
+        style: str,
+        needed_opt: str | None = None,
+    ) -> None:
+        if style != "no":
+            if not needed_opt or self.hasopt(needed_opt):
+                reports: list[BaseReport] = self.getreports(which_reports)
+                if not reports:
+                    return
+                self.write_sep("=", sep_title)
+                if style == "line":
+                    for rep in reports:
+                        line = self._getcrashline(rep)
+                        self.write_line(line)
+                else:
+                    for rep in reports:
+                        msg = self._getfailureheadline(rep)
+                        self.write_sep("_", msg, red=True, bold=True)
+                        self._outrep_summary(rep)
+                        self._handle_teardown_sections(rep.nodeid)
 
     def summary_errors(self) -> None:
         if self.config.option.tbstyle != "no":
-            reports: List[BaseReport] = self.getreports("error")
+            reports: list[BaseReport] = self.getreports("error")
             if not reports:
                 return
             self.write_sep("=", "ERRORS")
@@ -1068,72 +1239,111 @@ def short_test_summary(self) -> None:
         if not self.reportchars:
             return
 
-        def show_simple(stat, lines: List[str]) -> None:
+        def show_simple(lines: list[str], *, stat: str) -> None:
             failed = self.stats.get(stat, [])
             if not failed:
                 return
-            termwidth = self._tw.fullwidth
             config = self.config
             for rep in failed:
-                line = _get_line_with_reprcrash_message(config, rep, termwidth)
+                color = _color_for_type.get(stat, _color_for_type_default)
+                line = _get_line_with_reprcrash_message(
+                    config, rep, self._tw, {color: True}
+                )
                 lines.append(line)
 
-        def show_xfailed(lines: List[str]) -> None:
+        def show_xfailed(lines: list[str]) -> None:
             xfailed = self.stats.get("xfailed", [])
             for rep in xfailed:
-                verbose_word = rep._get_verbose_word(self.config)
-                pos = _get_pos(self.config, rep)
-                lines.append(f"{verbose_word} {pos}")
+                verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
+                    self.config, {_color_for_type["warnings"]: True}
+                )
+                markup_word = self._tw.markup(verbose_word, **verbose_markup)
+                nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
+                line = f"{markup_word} {nodeid}"
                 reason = rep.wasxfail
                 if reason:
-                    lines.append("  " + str(reason))
+                    line += " - " + str(reason)
+
+                lines.append(line)
 
-        def show_xpassed(lines: List[str]) -> None:
+        def show_xpassed(lines: list[str]) -> None:
             xpassed = self.stats.get("xpassed", [])
             for rep in xpassed:
-                verbose_word = rep._get_verbose_word(self.config)
-                pos = _get_pos(self.config, rep)
+                verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
+                    self.config, {_color_for_type["warnings"]: True}
+                )
+                markup_word = self._tw.markup(verbose_word, **verbose_markup)
+                nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
+                line = f"{markup_word} {nodeid}"
                 reason = rep.wasxfail
-                lines.append(f"{verbose_word} {pos} {reason}")
+                if reason:
+                    line += " - " + str(reason)
+                lines.append(line)
 
-        def show_skipped(lines: List[str]) -> None:
-            skipped: List[CollectReport] = self.stats.get("skipped", [])
+        def show_skipped_folded(lines: list[str]) -> None:
+            skipped: list[CollectReport] = self.stats.get("skipped", [])
             fskips = _folded_skips(self.startpath, skipped) if skipped else []
             if not fskips:
                 return
-            verbose_word = skipped[0]._get_verbose_word(self.config)
+            verbose_word, verbose_markup = skipped[0]._get_verbose_word_with_markup(
+                self.config, {_color_for_type["warnings"]: True}
+            )
+            markup_word = self._tw.markup(verbose_word, **verbose_markup)
+            prefix = "Skipped: "
             for num, fspath, lineno, reason in fskips:
-                if reason.startswith("Skipped: "):
-                    reason = reason[9:]
+                if reason.startswith(prefix):
+                    reason = reason[len(prefix) :]
                 if lineno is not None:
-                    lines.append(
-                        "%s [%d] %s:%d: %s"
-                        % (verbose_word, num, fspath, lineno, reason)
-                    )
+                    lines.append(f"{markup_word} [{num}] {fspath}:{lineno}: {reason}")
                 else:
-                    lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason))
+                    lines.append(f"{markup_word} [{num}] {fspath}: {reason}")
+
+        def show_skipped_unfolded(lines: list[str]) -> None:
+            skipped: list[CollectReport] = self.stats.get("skipped", [])
+
+            for rep in skipped:
+                assert rep.longrepr is not None
+                assert isinstance(rep.longrepr, tuple), (rep, rep.longrepr)
+                assert len(rep.longrepr) == 3, (rep, rep.longrepr)
 
-        REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = {
+                verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
+                    self.config, {_color_for_type["warnings"]: True}
+                )
+                markup_word = self._tw.markup(verbose_word, **verbose_markup)
+                nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
+                line = f"{markup_word} {nodeid}"
+                reason = rep.longrepr[2]
+                if reason:
+                    line += " - " + str(reason)
+                lines.append(line)
+
+        def show_skipped(lines: list[str]) -> None:
+            if self.foldskipped:
+                show_skipped_folded(lines)
+            else:
+                show_skipped_unfolded(lines)
+
+        REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = {
             "x": show_xfailed,
             "X": show_xpassed,
-            "f": partial(show_simple, "failed"),
+            "f": partial(show_simple, stat="failed"),
             "s": show_skipped,
-            "p": partial(show_simple, "passed"),
-            "E": partial(show_simple, "error"),
+            "p": partial(show_simple, stat="passed"),
+            "E": partial(show_simple, stat="error"),
         }
 
-        lines: List[str] = []
+        lines: list[str] = []
         for char in self.reportchars:
             action = REPORTCHAR_ACTIONS.get(char)
             if action:  # skipping e.g. "P" (passed with output) here.
                 action(lines)
 
         if lines:
-            self.write_sep("=", "short test summary info")
+            self.write_sep("=", "short test summary info", cyan=True, bold=True)
             for line in lines:
                 self.write_line(line)
 
-    def _get_main_color(self) -> Tuple[str, List[str]]:
+    def _get_main_color(self) -> tuple[str, list[str]]:
         if self._main_color is None or self._known_types is None or self._is_last_item:
             self._set_main_color()
             assert self._main_color
@@ -1153,22 +1363,22 @@ def _determine_main_color(self, unknown_type_seen: bool) -> str:
         return main_color
 
     def _set_main_color(self) -> None:
-        unknown_types: List[str] = []
-        for found_type in self.stats.keys():
+        unknown_types: list[str] = []
+        for found_type in self.stats:
             if found_type:  # setup/teardown reports have an empty key, ignore them
                 if found_type not in KNOWN_TYPES and found_type not in unknown_types:
                     unknown_types.append(found_type)
         self._known_types = list(KNOWN_TYPES) + unknown_types
         self._main_color = self._determine_main_color(bool(unknown_types))
 
-    def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]:
+    def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]:
         """
         Build the parts used in the last summary stats line.
 
         The summary stats line is the line shown at the end, "=== 12 passed, 2 errors in Xs===".
 
         This function builds a list of the "parts" that make up for the text in that line, in
-        the example above it would be:
+        the example above it would be::
 
             [
                 ("12 passed", {"green": True}),
@@ -1186,14 +1396,14 @@ def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], s
         else:
             return self._build_normal_summary_stats_line()
 
-    def _get_reports_to_display(self, key: str) -> List[Any]:
+    def _get_reports_to_display(self, key: str) -> list[Any]:
         """Get test/collection reports for the given status key, such as `passed` or `error`."""
         reports = self.stats.get(key, [])
         return [x for x in reports if getattr(x, "count_towards_summary", True)]
 
     def _build_normal_summary_stats_line(
         self,
-    ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]:
+    ) -> tuple[list[tuple[str, dict[str, bool]]], str]:
         main_color, known_types = self._get_main_color()
         parts = []
 
@@ -1203,7 +1413,7 @@ def _build_normal_summary_stats_line(
                 count = len(reports)
                 color = _color_for_type.get(key, _color_for_type_default)
                 markup = {color: True, "bold": color == main_color}
-                parts.append(("%d %s" % pluralize(count, key), markup))
+                parts.append(("%d %s" % pluralize(count, key), markup))  # noqa: UP031
 
         if not parts:
             parts = [("no tests ran", {_color_for_type_default: True})]
@@ -1212,7 +1422,7 @@ def _build_normal_summary_stats_line(
 
     def _build_collect_only_summary_stats_line(
         self,
-    ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]:
+    ) -> tuple[list[tuple[str, dict[str, bool]]], str]:
         deselected = len(self._get_reports_to_display("deselected"))
         errors = len(self._get_reports_to_display("error"))
 
@@ -1222,7 +1432,7 @@ def _build_collect_only_summary_stats_line(
 
         elif deselected == 0:
             main_color = "green"
-            collected_output = "%d %s collected" % pluralize(self._numcollected, "test")
+            collected_output = "%d %s collected" % pluralize(self._numcollected, "test")  # noqa: UP031
             parts = [(collected_output, {main_color: True})]
         else:
             all_tests_were_deselected = self._numcollected == deselected
@@ -1238,17 +1448,22 @@ def _build_collect_only_summary_stats_line(
 
         if errors:
             main_color = _color_for_type["error"]
-            parts += [("%d %s" % pluralize(errors, "error"), {main_color: True})]
+            parts += [("%d %s" % pluralize(errors, "error"), {main_color: True})]  # noqa: UP031
 
         return parts, main_color
 
 
-def _get_pos(config: Config, rep: BaseReport):
+def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport):
     nodeid = config.cwd_relative_nodeid(rep.nodeid)
-    return nodeid
+    path, *parts = nodeid.split("::")
+    if parts:
+        parts_markup = tw.markup("::".join(parts), bold=True)
+        return path + "::" + parts_markup
+    else:
+        return path
 
 
-def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]:
+def _format_trimmed(format: str, msg: str, available_width: int) -> str | None:
     """Format msg into format, ellipsizing it if doesn't fit in available_width.
 
     Returns None if even the ellipsis can't fit.
@@ -1274,13 +1489,16 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str
 
 
 def _get_line_with_reprcrash_message(
-    config: Config, rep: BaseReport, termwidth: int
+    config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool]
 ) -> str:
     """Get summary line for a report, trying to add reprcrash message."""
-    verbose_word = rep._get_verbose_word(config)
-    pos = _get_pos(config, rep)
+    verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
+        config, word_markup
+    )
+    word = tw.markup(verbose_word, **verbose_markup)
+    node = _get_node_id_with_markup(tw, config, rep)
 
-    line = f"{verbose_word} {pos}"
+    line = f"{word} {node}"
     line_width = wcswidth(line)
 
     try:
@@ -1289,8 +1507,13 @@ def _get_line_with_reprcrash_message(
     except AttributeError:
         pass
     else:
-        available_width = termwidth - line_width
-        msg = _format_trimmed(" - {}", msg, available_width)
+        if (
+            running_on_ci() or config.option.verbose >= 2
+        ) and not config.option.force_short_summary:
+            msg = f" - {msg}"
+        else:
+            available_width = tw.fullwidth - line_width
+            msg = _format_trimmed(" - {}", msg, available_width)
         if msg is not None:
             line += msg
 
@@ -1300,8 +1523,8 @@ def _get_line_with_reprcrash_message(
 def _folded_skips(
     startpath: Path,
     skipped: Sequence[CollectReport],
-) -> List[Tuple[int, str, Optional[int], str]]:
-    d: Dict[Tuple[str, Optional[int], str], List[CollectReport]] = {}
+) -> list[tuple[int, str, int | None, str]]:
+    d: dict[tuple[str, int | None, str], list[CollectReport]] = {}
     for event in skipped:
         assert event.longrepr is not None
         assert isinstance(event.longrepr, tuple), (event, event.longrepr)
@@ -1318,11 +1541,11 @@ def _folded_skips(
             and "skip" in keywords
             and "pytestmark" not in keywords
         ):
-            key: Tuple[str, Optional[int], str] = (fspath, None, reason)
+            key: tuple[str, int | None, str] = (fspath, None, reason)
         else:
             key = (fspath, lineno, reason)
         d.setdefault(key, []).append(event)
-    values: List[Tuple[int, str, Optional[int], str]] = []
+    values: list[tuple[int, str, int | None, str]] = []
     for key, events in d.items():
         values.append((len(events), *key))
     return values
@@ -1337,7 +1560,7 @@ def _folded_skips(
 _color_for_type_default = "yellow"
 
 
-def pluralize(count: int, noun: str) -> Tuple[int, str]:
+def pluralize(count: int, noun: str) -> tuple[int, str]:
     # No need to pluralize words such as `failed` or `passed`.
     if noun not in ["error", "warnings", "test"]:
         return count, noun
@@ -1350,11 +1573,11 @@ def pluralize(count: int, noun: str) -> Tuple[int, str]:
     return count, noun + "s" if count != 1 else noun
 
 
-def _plugin_nameversions(plugininfo) -> List[str]:
-    values: List[str] = []
+def _plugin_nameversions(plugininfo) -> list[str]:
+    values: list[str] = []
     for plugin, dist in plugininfo:
         # Gets us name and version!
-        name = "{dist.project_name}-{dist.version}".format(dist=dist)
+        name = f"{dist.project_name}-{dist.version}"
         # Questionable convenience, but it keeps things short.
         if name.startswith("pytest-"):
             name = name[7:]
@@ -1373,13 +1596,36 @@ def format_session_duration(seconds: float) -> str:
         return f"{seconds:.2f}s ({dt})"
 
 
+def format_node_duration(seconds: float) -> str:
+    """Format the given seconds in a human readable manner to show in the test progress."""
+    # The formatting is designed to be compact and readable, with at most 7 characters
+    # for durations below 100 hours.
+    if seconds < 0.00001:
+        return f" {seconds * 1000000:.3f}us"
+    if seconds < 0.0001:
+        return f" {seconds * 1000000:.2f}us"
+    if seconds < 0.001:
+        return f" {seconds * 1000000:.1f}us"
+    if seconds < 0.01:
+        return f" {seconds * 1000:.3f}ms"
+    if seconds < 0.1:
+        return f" {seconds * 1000:.2f}ms"
+    if seconds < 1:
+        return f" {seconds * 1000:.1f}ms"
+    if seconds < 60:
+        return f" {seconds:.3f}s"
+    if seconds < 3600:
+        return f" {seconds // 60:.0f}m {seconds % 60:.0f}s"
+    return f" {seconds // 3600:.0f}h {(seconds % 3600) // 60:.0f}m"
+
+
 def _get_raw_skip_reason(report: TestReport) -> str:
     """Get the reason string of a skip/xfail/xpass test report.
 
     The string is just the part given by the user.
     """
     if hasattr(report, "wasxfail"):
-        reason = cast(str, report.wasxfail)
+        reason = report.wasxfail
         if reason.startswith("reason: "):
             reason = reason[len("reason: ") :]
         return reason
diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py
index 43341e739a0..eb57783be26 100644
--- a/src/_pytest/threadexception.py
+++ b/src/_pytest/threadexception.py
@@ -1,88 +1,152 @@
+from __future__ import annotations
+
+import collections
+from collections.abc import Callable
+import functools
+import sys
 import threading
 import traceback
+from typing import NamedTuple
+from typing import TYPE_CHECKING
 import warnings
-from types import TracebackType
-from typing import Any
-from typing import Callable
-from typing import Generator
-from typing import Optional
-from typing import Type
 
+from _pytest.config import Config
+from _pytest.nodes import Item
+from _pytest.stash import StashKey
+from _pytest.tracemalloc import tracemalloc_message
 import pytest
 
 
-# Copied from cpython/Lib/test/support/threading_helper.py, with modifications.
-class catch_threading_exception:
-    """Context manager catching threading.Thread exception using
-    threading.excepthook.
-
-    Storing exc_value using a custom hook can create a reference cycle. The
-    reference cycle is broken explicitly when the context manager exits.
-
-    Storing thread using a custom hook can resurrect it if it is set to an
-    object which is being finalized. Exiting the context manager clears the
-    stored object.
-
-    Usage:
-        with threading_helper.catch_threading_exception() as cm:
-            # code spawning a thread which raises an exception
-            ...
-            # check the thread exception: use cm.args
-            ...
-        # cm.args attribute no longer exists at this point
-        # (to break a reference cycle)
-    """
-
-    def __init__(self) -> None:
-        self.args: Optional["threading.ExceptHookArgs"] = None
-        self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None
-
-    def _hook(self, args: "threading.ExceptHookArgs") -> None:
-        self.args = args
-
-    def __enter__(self) -> "catch_threading_exception":
-        self._old_hook = threading.excepthook
-        threading.excepthook = self._hook
-        return self
-
-    def __exit__(
-        self,
-        exc_type: Optional[Type[BaseException]],
-        exc_val: Optional[BaseException],
-        exc_tb: Optional[TracebackType],
-    ) -> None:
-        assert self._old_hook is not None
-        threading.excepthook = self._old_hook
-        self._old_hook = None
-        del self.args
-
-
-def thread_exception_runtest_hook() -> Generator[None, None, None]:
-    with catch_threading_exception() as cm:
-        yield
-        if cm.args:
-            thread_name = "<unknown>" if cm.args.thread is None else cm.args.thread.name
-            msg = f"Exception in thread {thread_name}\n\n"
-            msg += "".join(
-                traceback.format_exception(
-                    cm.args.exc_type,
-                    cm.args.exc_value,
-                    cm.args.exc_traceback,
-                )
+if TYPE_CHECKING:
+    pass
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import ExceptionGroup
+
+
+class ThreadExceptionMeta(NamedTuple):
+    msg: str
+    cause_msg: str
+    exc_value: BaseException | None
+
+
+thread_exceptions: StashKey[collections.deque[ThreadExceptionMeta | BaseException]] = (
+    StashKey()
+)
+
+
+def collect_thread_exception(config: Config) -> None:
+    pop_thread_exception = config.stash[thread_exceptions].pop
+    errors: list[pytest.PytestUnhandledThreadExceptionWarning | RuntimeError] = []
+    meta = None
+    hook_error = None
+    try:
+        while True:
+            try:
+                meta = pop_thread_exception()
+            except IndexError:
+                break
+
+            if isinstance(meta, BaseException):
+                hook_error = RuntimeError("Failed to process thread exception")
+                hook_error.__cause__ = meta
+                errors.append(hook_error)
+                continue
+
+            msg = meta.msg
+            try:
+                warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg))
+            except pytest.PytestUnhandledThreadExceptionWarning as e:
+                # This except happens when the warning is treated as an error (e.g. `-Werror`).
+                if meta.exc_value is not None:
+                    # Exceptions have a better way to show the traceback, but
+                    # warnings do not, so hide the traceback from the msg and
+                    # set the cause so the traceback shows up in the right place.
+                    e.args = (meta.cause_msg,)
+                    e.__cause__ = meta.exc_value
+                errors.append(e)
+
+        if len(errors) == 1:
+            raise errors[0]
+        if errors:
+            raise ExceptionGroup("multiple thread exception warnings", errors)
+    finally:
+        del errors, meta, hook_error
+
+
+def cleanup(
+    *, config: Config, prev_hook: Callable[[threading.ExceptHookArgs], object]
+) -> None:
+    try:
+        try:
+            # We don't join threads here, so exceptions raised from any
+            # threads still running by the time _threading_atexits joins them
+            # do not get captured (see #13027).
+            collect_thread_exception(config)
+        finally:
+            threading.excepthook = prev_hook
+    finally:
+        del config.stash[thread_exceptions]
+
+
+def thread_exception_hook(
+    args: threading.ExceptHookArgs,
+    /,
+    *,
+    append: Callable[[ThreadExceptionMeta | BaseException], object],
+) -> None:
+    try:
+        # we need to compute these strings here as they might change after
+        # the excepthook finishes and before the metadata object is
+        # collected by a pytest hook
+        thread_name = "<unknown>" if args.thread is None else args.thread.name
+        summary = f"Exception in thread {thread_name}"
+        traceback_message = "\n\n" + "".join(
+            traceback.format_exception(
+                args.exc_type,
+                args.exc_value,
+                args.exc_traceback,
+            )
+        )
+        tracemalloc_tb = "\n" + tracemalloc_message(args.thread)
+        msg = summary + traceback_message + tracemalloc_tb
+        cause_msg = summary + tracemalloc_tb
+
+        append(
+            ThreadExceptionMeta(
+                # Compute these strings here as they might change later
+                msg=msg,
+                cause_msg=cause_msg,
+                exc_value=args.exc_value,
             )
-            warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg))
+        )
+    except BaseException as e:
+        append(e)
+        # Raising this will cause the exception to be logged twice, once in our
+        # collect_thread_exception and once by sys.excepthook
+        # which is fine - this should never happen anyway and if it does
+        # it should probably be reported as a pytest bug.
+        raise
+
+
+def pytest_configure(config: Config) -> None:
+    prev_hook = threading.excepthook
+    deque: collections.deque[ThreadExceptionMeta | BaseException] = collections.deque()
+    config.stash[thread_exceptions] = deque
+    config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook))
+    threading.excepthook = functools.partial(thread_exception_hook, append=deque.append)
 
 
-@pytest.hookimpl(hookwrapper=True, trylast=True)
-def pytest_runtest_setup() -> Generator[None, None, None]:
-    yield from thread_exception_runtest_hook()
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_setup(item: Item) -> None:
+    collect_thread_exception(item.config)
 
 
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_runtest_call() -> Generator[None, None, None]:
-    yield from thread_exception_runtest_hook()
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_call(item: Item) -> None:
+    collect_thread_exception(item.config)
 
 
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_runtest_teardown() -> Generator[None, None, None]:
-    yield from thread_exception_runtest_hook()
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_teardown(item: Item) -> None:
+    collect_thread_exception(item.config)
diff --git a/src/_pytest/timing.py b/src/_pytest/timing.py
index 925163a5858..4422037a9d9 100644
--- a/src/_pytest/timing.py
+++ b/src/_pytest/timing.py
@@ -5,8 +5,48 @@
 
 Fixture "mock_timing" also interacts with this module for pytest's own tests.
 """
+
+from __future__ import annotations
+
+import dataclasses
+from datetime import datetime
 from time import perf_counter
 from time import sleep
 from time import time
+from typing import TYPE_CHECKING
+
+
+if TYPE_CHECKING:
+    from pytest import MonkeyPatch
+
+
+@dataclasses.dataclass
+class MockTiming:
+    """Mocks _pytest.timing with a known object that can be used to control timing in tests
+    deterministically.
+
+    pytest itself should always use functions from `_pytest.timing` instead of `time` directly.
+
+    This then allows us more control over time during testing, if testing code also
+    uses `_pytest.timing` functions.
+
+    Time is static, and only advances through `sleep` calls, thus tests might sleep over large
+    numbers and obtain accurate time() calls at the end, making tests reliable and instant."""
+
+    _current_time: float = datetime(2020, 5, 22, 14, 20, 50).timestamp()
+
+    def sleep(self, seconds: float) -> None:
+        self._current_time += seconds
+
+    def time(self) -> float:
+        return self._current_time
+
+    def patch(self, monkeypatch: MonkeyPatch) -> None:
+        from _pytest import timing  # noqa: PLW0406
+
+        monkeypatch.setattr(timing, "sleep", self.sleep)
+        monkeypatch.setattr(timing, "time", self.time)
+        monkeypatch.setattr(timing, "perf_counter", self.time)
+
 
 __all__ = ["perf_counter", "sleep", "time"]
diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py
index f901fd5727c..c5b51c87741 100644
--- a/src/_pytest/tmpdir.py
+++ b/src/_pytest/tmpdir.py
@@ -1,42 +1,63 @@
+# mypy: allow-untyped-defs
 """Support for providing temporary directories to test functions."""
+
+from __future__ import annotations
+
+from collections.abc import Generator
+import dataclasses
 import os
+from pathlib import Path
 import re
-import sys
+from shutil import rmtree
 import tempfile
-from pathlib import Path
-from typing import Optional
-
-import attr
+from typing import Any
+from typing import final
+from typing import Literal
 
+from .pathlib import cleanup_dead_symlinks
 from .pathlib import LOCK_TIMEOUT
 from .pathlib import make_numbered_dir
 from .pathlib import make_numbered_dir_with_cleanup
 from .pathlib import rm_rf
-from _pytest.compat import final
+from _pytest.compat import get_user_id
 from _pytest.config import Config
+from _pytest.config import ExitCode
+from _pytest.config import hookimpl
+from _pytest.config.argparsing import Parser
 from _pytest.deprecated import check_ispytest
 from _pytest.fixtures import fixture
 from _pytest.fixtures import FixtureRequest
 from _pytest.monkeypatch import MonkeyPatch
+from _pytest.nodes import Item
+from _pytest.reports import TestReport
+from _pytest.stash import StashKey
+
+
+tmppath_result_key = StashKey[dict[str, bool]]()
+RetentionType = Literal["all", "failed", "none"]
 
 
 @final
-@attr.s(init=False)
+@dataclasses.dataclass
 class TempPathFactory:
-    """Factory for temporary directories under the common base temp directory.
-
-    The base directory can be configured using the ``--basetemp`` option.
+    """Factory for temporary directories under the common base temp directory,
+    as discussed at :ref:`temporary directory location and retention`.
     """
 
-    _given_basetemp = attr.ib(type=Optional[Path])
-    _trace = attr.ib()
-    _basetemp = attr.ib(type=Optional[Path])
+    _given_basetemp: Path | None
+    # pluggy TagTracerSub, not currently exposed, so Any.
+    _trace: Any
+    _basetemp: Path | None
+    _retention_count: int
+    _retention_policy: RetentionType
 
     def __init__(
         self,
-        given_basetemp: Optional[Path],
+        given_basetemp: Path | None,
+        retention_count: int,
+        retention_policy: RetentionType,
         trace,
-        basetemp: Optional[Path] = None,
+        basetemp: Path | None = None,
         *,
         _ispytest: bool = False,
     ) -> None:
@@ -49,6 +70,8 @@ def __init__(
             # Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012).
             self._given_basetemp = Path(os.path.abspath(str(given_basetemp)))
         self._trace = trace
+        self._retention_count = retention_count
+        self._retention_policy = retention_policy
         self._basetemp = basetemp
 
     @classmethod
@@ -57,15 +80,29 @@ def from_config(
         config: Config,
         *,
         _ispytest: bool = False,
-    ) -> "TempPathFactory":
+    ) -> TempPathFactory:
         """Create a factory according to pytest configuration.
 
         :meta private:
         """
         check_ispytest(_ispytest)
+        count = int(config.getini("tmp_path_retention_count"))
+        if count < 0:
+            raise ValueError(
+                f"tmp_path_retention_count must be >= 0. Current input: {count}."
+            )
+
+        policy = config.getini("tmp_path_retention_policy")
+        if policy not in ("all", "failed", "none"):
+            raise ValueError(
+                f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}."
+            )
+
         return cls(
             given_basetemp=config.option.basetemp,
             trace=config.trace.get("tmpdir"),
+            retention_count=count,
+            retention_policy=policy,
             _ispytest=True,
         )
 
@@ -100,7 +137,11 @@ def mktemp(self, basename: str, numbered: bool = True) -> Path:
         return p
 
     def getbasetemp(self) -> Path:
-        """Return the base temporary directory, creating it if needed."""
+        """Return the base temporary directory, creating it if needed.
+
+        :returns:
+            The base temporary directory.
+        """
         if self._basetemp is not None:
             return self._basetemp
 
@@ -129,23 +170,23 @@ def getbasetemp(self) -> Path:
             # Also, to keep things private, fixup any world-readable temp
             # rootdir's permissions. Historically 0o755 was used, so we can't
             # just error out on this, at least for a while.
-            if sys.platform != "win32":
-                uid = os.getuid()
+            uid = get_user_id()
+            if uid is not None:
                 rootdir_stat = rootdir.stat()
-                # getuid shouldn't fail, but cpython defines such a case.
-                # Let's hope for the best.
-                if uid != -1:
-                    if rootdir_stat.st_uid != uid:
-                        raise OSError(
-                            f"The temporary directory {rootdir} is not owned by the current user. "
-                            "Fix this and try again."
-                        )
-                    if (rootdir_stat.st_mode & 0o077) != 0:
-                        os.chmod(rootdir, rootdir_stat.st_mode & ~0o077)
+                if rootdir_stat.st_uid != uid:
+                    raise OSError(
+                        f"The temporary directory {rootdir} is not owned by the current user. "
+                        "Fix this and try again."
+                    )
+                if (rootdir_stat.st_mode & 0o077) != 0:
+                    os.chmod(rootdir, rootdir_stat.st_mode & ~0o077)
+            keep = self._retention_count
+            if self._retention_policy == "none":
+                keep = 0
             basetemp = make_numbered_dir_with_cleanup(
                 prefix="pytest-",
                 root=rootdir,
-                keep=3,
+                keep=keep,
                 lock_timeout=LOCK_TIMEOUT,
                 mode=0o700,
             )
@@ -155,14 +196,15 @@ def getbasetemp(self) -> Path:
         return basetemp
 
 
-def get_user() -> Optional[str]:
+def get_user() -> str | None:
     """Return the current user name, or None if getuser() does not work
     in the current environment (see #1010)."""
-    import getpass
-
     try:
+        # In some exotic environments, getpass may not be importable.
+        import getpass
+
         return getpass.getuser()
-    except (ImportError, KeyError):
+    except (ImportError, OSError, KeyError):
         return None
 
 
@@ -179,6 +221,21 @@ def pytest_configure(config: Config) -> None:
     mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False)
 
 
+def pytest_addoption(parser: Parser) -> None:
+    parser.addini(
+        "tmp_path_retention_count",
+        help="How many sessions should we keep the `tmp_path` directories, according to `tmp_path_retention_policy`.",
+        default=3,
+    )
+
+    parser.addini(
+        "tmp_path_retention_policy",
+        help="Controls which directories created by the `tmp_path` fixture are kept around, based on test outcome. "
+        "(all/failed/none)",
+        default="all",
+    )
+
+
 @fixture(scope="session")
 def tmp_path_factory(request: FixtureRequest) -> TempPathFactory:
     """Return a :class:`pytest.TempPathFactory` instance for the test session."""
@@ -195,17 +252,62 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path:
 
 
 @fixture
-def tmp_path(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path:
-    """Return a temporary directory path object which is unique to each test
-    function invocation, created as a sub directory of the base temporary
-    directory.
+def tmp_path(
+    request: FixtureRequest, tmp_path_factory: TempPathFactory
+) -> Generator[Path]:
+    """Return a temporary directory (as :class:`pathlib.Path` object)
+    which is unique to each test function invocation.
+    The temporary directory is created as a subdirectory
+    of the base temporary directory, with configurable retention,
+    as discussed in :ref:`temporary directory location and retention`.
+    """
+    path = _mk_tmp(request, tmp_path_factory)
+    yield path
 
-    By default, a new base temporary directory is created each test session,
-    and old bases are removed after 3 sessions, to aid in debugging. If
-    ``--basetemp`` is used then it is cleared each session. See :ref:`base
-    temporary directory`.
+    # Remove the tmpdir if the policy is "failed" and the test passed.
+    tmp_path_factory: TempPathFactory = request.session.config._tmp_path_factory  # type: ignore
+    policy = tmp_path_factory._retention_policy
+    result_dict = request.node.stash[tmppath_result_key]
 
-    The returned object is a :class:`pathlib.Path` object.
+    if policy == "failed" and result_dict.get("call", True):
+        # We do a "best effort" to remove files, but it might not be possible due to some leaked resource,
+        # permissions, etc, in which case we ignore it.
+        rmtree(path, ignore_errors=True)
+
+    del request.node.stash[tmppath_result_key]
+
+
+def pytest_sessionfinish(session, exitstatus: int | ExitCode):
+    """After each session, remove base directory if all the tests passed,
+    the policy is "failed", and the basetemp is not specified by a user.
     """
+    tmp_path_factory: TempPathFactory = session.config._tmp_path_factory
+    basetemp = tmp_path_factory._basetemp
+    if basetemp is None:
+        return
+
+    policy = tmp_path_factory._retention_policy
+    if (
+        exitstatus == 0
+        and policy == "failed"
+        and tmp_path_factory._given_basetemp is None
+    ):
+        if basetemp.is_dir():
+            # We do a "best effort" to remove files, but it might not be possible due to some leaked resource,
+            # permissions, etc, in which case we ignore it.
+            rmtree(basetemp, ignore_errors=True)
+
+    # Remove dead symlinks.
+    if basetemp.is_dir():
+        cleanup_dead_symlinks(basetemp)
+
 
-    return _mk_tmp(request, tmp_path_factory)
+@hookimpl(wrapper=True, tryfirst=True)
+def pytest_runtest_makereport(
+    item: Item, call
+) -> Generator[None, TestReport, TestReport]:
+    rep = yield
+    assert rep.when is not None
+    empty: dict[str, bool] = {}
+    item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed
+    return rep
diff --git a/src/_pytest/tracemalloc.py b/src/_pytest/tracemalloc.py
new file mode 100644
index 00000000000..5d0b19855c7
--- /dev/null
+++ b/src/_pytest/tracemalloc.py
@@ -0,0 +1,24 @@
+from __future__ import annotations
+
+
+def tracemalloc_message(source: object) -> str:
+    if source is None:
+        return ""
+
+    try:
+        import tracemalloc
+    except ImportError:
+        return ""
+
+    tb = tracemalloc.get_object_traceback(source)
+    if tb is not None:
+        formatted_tb = "\n".join(tb.format())
+        # Use a leading new line to better separate the (large) output
+        # from the traceback to the previous warning text.
+        return f"\nObject allocated at:\n{formatted_tb}"
+    # No need for a leading new line.
+    url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings"
+    return (
+        "Enable tracemalloc to get traceback where the object was allocated.\n"
+        f"See {url} for more info."
+    )
diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py
index 108095bfcbe..04d50b53090 100644
--- a/src/_pytest/unittest.py
+++ b/src/_pytest/unittest.py
@@ -1,21 +1,20 @@
+# mypy: allow-untyped-defs
 """Discover and run std-library "unittest" style tests."""
+
+from __future__ import annotations
+
+from collections.abc import Callable
+from collections.abc import Generator
+from collections.abc import Iterable
+import inspect
 import sys
 import traceback
 import types
 from typing import Any
-from typing import Callable
-from typing import Generator
-from typing import Iterable
-from typing import List
-from typing import Optional
-from typing import Tuple
-from typing import Type
 from typing import TYPE_CHECKING
 from typing import Union
 
 import _pytest._code
-import pytest
-from _pytest.compat import getimfunc
 from _pytest.compat import is_async_function
 from _pytest.config import hookimpl
 from _pytest.fixtures import FixtureRequest
@@ -27,34 +26,44 @@
 from _pytest.outcomes import xfail
 from _pytest.python import Class
 from _pytest.python import Function
-from _pytest.python import PyCollector
+from _pytest.python import Module
 from _pytest.runner import CallInfo
-from _pytest.scope import Scope
+import pytest
+
+
+if sys.version_info[:2] < (3, 11):
+    from exceptiongroup import ExceptionGroup
 
 if TYPE_CHECKING:
     import unittest
+
     import twisted.trial.unittest
 
-    _SysExcInfoType = Union[
-        Tuple[Type[BaseException], BaseException, types.TracebackType],
-        Tuple[None, None, None],
-    ]
+
+_SysExcInfoType = Union[
+    tuple[type[BaseException], BaseException, types.TracebackType],
+    tuple[None, None, None],
+]
 
 
 def pytest_pycollect_makeitem(
-    collector: PyCollector, name: str, obj: object
-) -> Optional["UnitTestCase"]:
-    # Has unittest been imported and is obj a subclass of its TestCase?
+    collector: Module | Class, name: str, obj: object
+) -> UnitTestCase | None:
     try:
+        # Has unittest been imported?
         ut = sys.modules["unittest"]
+        # Is obj a subclass of unittest.TestCase?
         # Type ignored because `ut` is an opaque module.
         if not issubclass(obj, ut.TestCase):  # type: ignore
             return None
     except Exception:
         return None
+    # Is obj a concrete class?
+    # Abstract classes can't be instantiated so no point collecting them.
+    if inspect.isabstract(obj):
+        return None
     # Yes, so let's collect it.
-    item: UnitTestCase = UnitTestCase.from_parent(collector, name=name, obj=obj)
-    return item
+    return UnitTestCase.from_parent(collector, name=name, obj=obj)
 
 
 class UnitTestCase(Class):
@@ -62,7 +71,15 @@ class UnitTestCase(Class):
     # to declare that our children do not support funcargs.
     nofuncargs = True
 
-    def collect(self) -> Iterable[Union[Item, Collector]]:
+    def newinstance(self):
+        # TestCase __init__ takes the method (test) name. The TestCase
+        # constructor treats the name "runTest" as a special no-op, so it can be
+        # used when a dummy instance is needed. While unittest.TestCase has a
+        # default, some subclasses omit the default (#9610), so always supply
+        # it.
+        return self.obj("runTest")
+
+    def collect(self) -> Iterable[Item | Collector]:
         from unittest import TestLoader
 
         cls = self.obj
@@ -71,148 +88,156 @@ def collect(self) -> Iterable[Union[Item, Collector]]:
 
         skipped = _is_skipped(cls)
         if not skipped:
-            self._inject_setup_teardown_fixtures(cls)
-            self._inject_setup_class_fixture()
+            self._register_unittest_setup_method_fixture(cls)
+            self._register_unittest_setup_class_fixture(cls)
+            self._register_setup_class_fixture()
+
+        self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid)
 
-        self.session._fixturemanager.parsefactories(self, unittest=True)
         loader = TestLoader()
         foundsomething = False
         for name in loader.getTestCaseNames(self.obj):
             x = getattr(self.obj, name)
             if not getattr(x, "__test__", True):
                 continue
-            funcobj = getimfunc(x)
-            yield TestCaseFunction.from_parent(self, name=name, callobj=funcobj)
+            yield TestCaseFunction.from_parent(self, name=name)
             foundsomething = True
 
         if not foundsomething:
             runtest = getattr(self.obj, "runTest", None)
             if runtest is not None:
                 ut = sys.modules.get("twisted.trial.unittest", None)
-                # Type ignored because `ut` is an opaque module.
-                if ut is None or runtest != ut.TestCase.runTest:  # type: ignore
+                if ut is None or runtest != ut.TestCase.runTest:
                     yield TestCaseFunction.from_parent(self, name="runTest")
 
-    def _inject_setup_teardown_fixtures(self, cls: type) -> None:
-        """Injects a hidden auto-use fixture to invoke setUpClass/setup_method and corresponding
-        teardown functions (#517)."""
-        class_fixture = _make_xunit_fixture(
-            cls,
-            "setUpClass",
-            "tearDownClass",
-            "doClassCleanups",
-            scope=Scope.Class,
-            pass_self=False,
-        )
-        if class_fixture:
-            cls.__pytest_class_setup = class_fixture  # type: ignore[attr-defined]
-
-        method_fixture = _make_xunit_fixture(
-            cls,
-            "setup_method",
-            "teardown_method",
-            None,
-            scope=Scope.Function,
-            pass_self=True,
-        )
-        if method_fixture:
-            cls.__pytest_method_setup = method_fixture  # type: ignore[attr-defined]
-
-
-def _make_xunit_fixture(
-    obj: type,
-    setup_name: str,
-    teardown_name: str,
-    cleanup_name: Optional[str],
-    scope: Scope,
-    pass_self: bool,
-):
-    setup = getattr(obj, setup_name, None)
-    teardown = getattr(obj, teardown_name, None)
-    if setup is None and teardown is None:
-        return None
-
-    if cleanup_name:
-        cleanup = getattr(obj, cleanup_name, lambda *args: None)
-    else:
-
-        def cleanup(*args):
-            pass
-
-    @pytest.fixture(
-        scope=scope.value,
-        autouse=True,
-        # Use a unique name to speed up lookup.
-        name=f"_unittest_{setup_name}_fixture_{obj.__qualname__}",
-    )
-    def fixture(self, request: FixtureRequest) -> Generator[None, None, None]:
-        if _is_skipped(self):
-            reason = self.__unittest_skip_why__
-            raise pytest.skip.Exception(reason, _use_item_location=True)
-        if setup is not None:
-            try:
-                if pass_self:
-                    setup(self, request.function)
-                else:
+    def _register_unittest_setup_class_fixture(self, cls: type) -> None:
+        """Register an auto-use fixture to invoke setUpClass and
+        tearDownClass (#517)."""
+        setup = getattr(cls, "setUpClass", None)
+        teardown = getattr(cls, "tearDownClass", None)
+        if setup is None and teardown is None:
+            return None
+        cleanup = getattr(cls, "doClassCleanups", lambda: None)
+
+        def process_teardown_exceptions() -> None:
+            # tearDown_exceptions is a list set in the class containing exc_infos for errors during
+            # teardown for the class.
+            exc_infos = getattr(cls, "tearDown_exceptions", None)
+            if not exc_infos:
+                return
+            exceptions = [exc for (_, exc, _) in exc_infos]
+            # If a single exception, raise it directly as this provides a more readable
+            # error (hopefully this will improve in #12255).
+            if len(exceptions) == 1:
+                raise exceptions[0]
+            else:
+                raise ExceptionGroup("Unittest class cleanup errors", exceptions)
+
+        def unittest_setup_class_fixture(
+            request: FixtureRequest,
+        ) -> Generator[None]:
+            cls = request.cls
+            if _is_skipped(cls):
+                reason = cls.__unittest_skip_why__
+                raise pytest.skip.Exception(reason, _use_item_location=True)
+            if setup is not None:
+                try:
                     setup()
-            # unittest does not call the cleanup function for every BaseException, so we
-            # follow this here.
-            except Exception:
-                if pass_self:
-                    cleanup(self)
-                else:
+                # unittest does not call the cleanup function for every BaseException, so we
+                # follow this here.
+                except Exception:
                     cleanup()
-
-                raise
-        yield
-        try:
-            if teardown is not None:
-                if pass_self:
-                    teardown(self, request.function)
-                else:
+                    process_teardown_exceptions()
+                    raise
+            yield
+            try:
+                if teardown is not None:
                     teardown()
-        finally:
-            if pass_self:
-                cleanup(self)
-            else:
+            finally:
                 cleanup()
+                process_teardown_exceptions()
+
+        self.session._fixturemanager._register_fixture(
+            # Use a unique name to speed up lookup.
+            name=f"_unittest_setUpClass_fixture_{cls.__qualname__}",
+            func=unittest_setup_class_fixture,
+            nodeid=self.nodeid,
+            scope="class",
+            autouse=True,
+        )
 
-    return fixture
+    def _register_unittest_setup_method_fixture(self, cls: type) -> None:
+        """Register an auto-use fixture to invoke setup_method and
+        teardown_method (#517)."""
+        setup = getattr(cls, "setup_method", None)
+        teardown = getattr(cls, "teardown_method", None)
+        if setup is None and teardown is None:
+            return None
+
+        def unittest_setup_method_fixture(
+            request: FixtureRequest,
+        ) -> Generator[None]:
+            self = request.instance
+            if _is_skipped(self):
+                reason = self.__unittest_skip_why__
+                raise pytest.skip.Exception(reason, _use_item_location=True)
+            if setup is not None:
+                setup(self, request.function)
+            yield
+            if teardown is not None:
+                teardown(self, request.function)
+
+        self.session._fixturemanager._register_fixture(
+            # Use a unique name to speed up lookup.
+            name=f"_unittest_setup_method_fixture_{cls.__qualname__}",
+            func=unittest_setup_method_fixture,
+            nodeid=self.nodeid,
+            scope="function",
+            autouse=True,
+        )
 
 
 class TestCaseFunction(Function):
     nofuncargs = True
-    _excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None
-    _testcase: Optional["unittest.TestCase"] = None
+    _excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None
+
+    def _getinstance(self):
+        assert isinstance(self.parent, UnitTestCase)
+        return self.parent.obj(self.name)
+
+    # Backward compat for pytest-django; can be removed after pytest-django
+    # updates + some slack.
+    @property
+    def _testcase(self):
+        return self.instance
 
     def setup(self) -> None:
         # A bound method to be called during teardown() if set (see 'runtest()').
-        self._explicit_tearDown: Optional[Callable[[], None]] = None
-        assert self.parent is not None
-        self._testcase = self.parent.obj(self.name)  # type: ignore[attr-defined]
-        self._obj = getattr(self._testcase, self.name)
-        if hasattr(self, "_request"):
-            self._request._fillfixtures()
+        self._explicit_tearDown: Callable[[], None] | None = None
+        super().setup()
 
     def teardown(self) -> None:
         if self._explicit_tearDown is not None:
             self._explicit_tearDown()
             self._explicit_tearDown = None
-        self._testcase = None
         self._obj = None
+        del self._instance
+        super().teardown()
 
-    def startTest(self, testcase: "unittest.TestCase") -> None:
+    def startTest(self, testcase: unittest.TestCase) -> None:
         pass
 
-    def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None:
+    def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None:
         # Unwrap potential exception info (see twisted trial support below).
         rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
         try:
-            excinfo = _pytest._code.ExceptionInfo[BaseException].from_exc_info(rawexcinfo)  # type: ignore[arg-type]
+            excinfo = _pytest._code.ExceptionInfo[BaseException].from_exc_info(
+                rawexcinfo  # type: ignore[arg-type]
+            )
             # Invoke the attributes to trigger storing the traceback
             # trial causes some issue there.
-            excinfo.value
-            excinfo.traceback
+            _ = excinfo.value
+            _ = excinfo.traceback
         except TypeError:
             try:
                 try:
@@ -228,7 +253,7 @@ def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None:
                 except BaseException:
                     fail(
                         "ERROR: Unknown Incompatible Exception "
-                        "representation:\n%r" % (rawexcinfo,),
+                        f"representation:\n{rawexcinfo!r}",
                         pytrace=False,
                     )
             except KeyboardInterrupt:
@@ -238,7 +263,7 @@ def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None:
         self.__dict__.setdefault("_excinfo", []).append(excinfo)
 
     def addError(
-        self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType"
+        self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType
     ) -> None:
         try:
             if isinstance(rawexcinfo[1], exit.Exception):
@@ -248,11 +273,11 @@ def addError(
         self._addexcinfo(rawexcinfo)
 
     def addFailure(
-        self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType"
+        self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType
     ) -> None:
         self._addexcinfo(rawexcinfo)
 
-    def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None:
+    def addSkip(self, testcase: unittest.TestCase, reason: str) -> None:
         try:
             raise pytest.skip.Exception(reason, _use_item_location=True)
         except skip.Exception:
@@ -260,8 +285,8 @@ def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None:
 
     def addExpectedFailure(
         self,
-        testcase: "unittest.TestCase",
-        rawexcinfo: "_SysExcInfoType",
+        testcase: unittest.TestCase,
+        rawexcinfo: _SysExcInfoType,
         reason: str = "",
     ) -> None:
         try:
@@ -271,8 +296,8 @@ def addExpectedFailure(
 
     def addUnexpectedSuccess(
         self,
-        testcase: "unittest.TestCase",
-        reason: Optional["twisted.trial.unittest.Todo"] = None,
+        testcase: unittest.TestCase,
+        reason: twisted.trial.unittest.Todo | None = None,
     ) -> None:
         msg = "Unexpected success"
         if reason:
@@ -283,23 +308,26 @@ def addUnexpectedSuccess(
         except fail.Exception:
             self._addexcinfo(sys.exc_info())
 
-    def addSuccess(self, testcase: "unittest.TestCase") -> None:
+    def addSuccess(self, testcase: unittest.TestCase) -> None:
         pass
 
-    def stopTest(self, testcase: "unittest.TestCase") -> None:
+    def stopTest(self, testcase: unittest.TestCase) -> None:
+        pass
+
+    def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None:
         pass
 
     def runtest(self) -> None:
         from _pytest.debugging import maybe_wrap_pytest_function_for_tracing
 
-        assert self._testcase is not None
+        testcase = self.instance
+        assert testcase is not None
 
         maybe_wrap_pytest_function_for_tracing(self)
 
         # Let the unittest framework handle async functions.
         if is_async_function(self.obj):
-            # Type ignored because self acts as the TestResult, but is not actually one.
-            self._testcase(result=self)  # type: ignore[arg-type]
+            testcase(result=self)
         else:
             # When --pdb is given, we want to postpone calling tearDown() otherwise
             # when entering the pdb prompt, tearDown() would have probably cleaned up
@@ -307,27 +335,31 @@ def runtest(self) -> None:
             # Arguably we could always postpone tearDown(), but this changes the moment where the
             # TestCase instance interacts with the results object, so better to only do it
             # when absolutely needed.
-            if self.config.getoption("usepdb") and not _is_skipped(self.obj):
-                self._explicit_tearDown = self._testcase.tearDown
-                setattr(self._testcase, "tearDown", lambda *args: None)
+            # We need to consider if the test itself is skipped, or the whole class.
+            assert isinstance(self.parent, UnitTestCase)
+            skipped = _is_skipped(self.obj) or _is_skipped(self.parent.obj)
+            if self.config.getoption("usepdb") and not skipped:
+                self._explicit_tearDown = testcase.tearDown
+                setattr(testcase, "tearDown", lambda *args: None)
 
             # We need to update the actual bound method with self.obj, because
             # wrap_pytest_function_for_tracing replaces self.obj by a wrapper.
-            setattr(self._testcase, self.name, self.obj)
+            setattr(testcase, self.name, self.obj)
             try:
-                self._testcase(result=self)  # type: ignore[arg-type]
+                testcase(result=self)
             finally:
-                delattr(self._testcase, self.name)
+                delattr(testcase, self.name)
 
-    def _prunetraceback(
+    def _traceback_filter(
         self, excinfo: _pytest._code.ExceptionInfo[BaseException]
-    ) -> None:
-        super()._prunetraceback(excinfo)
-        traceback = excinfo.traceback.filter(
-            lambda x: not x.frame.f_globals.get("__unittest")
+    ) -> _pytest._code.Traceback:
+        traceback = super()._traceback_filter(excinfo)
+        ntraceback = traceback.filter(
+            lambda x: not x.frame.f_globals.get("__unittest"),
         )
-        if traceback:
-            excinfo.traceback = traceback
+        if not ntraceback:
+            ntraceback = traceback
+        return ntraceback
 
 
 @hookimpl(tryfirst=True)
@@ -345,11 +377,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None:
     # its own nose.SkipTest. For unittest TestCases, SkipTest is already
     # handled internally, and doesn't reach here.
     unittest = sys.modules.get("unittest")
-    if (
-        unittest
-        and call.excinfo
-        and isinstance(call.excinfo.value, unittest.SkipTest)  # type: ignore[attr-defined]
-    ):
+    if unittest and call.excinfo and isinstance(call.excinfo.value, unittest.SkipTest):
         excinfo = call.excinfo
         call2 = CallInfo[None].from_call(
             lambda: pytest.skip(str(excinfo.value)), call.when
@@ -358,14 +386,21 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> None:
 
 
 # Twisted trial support.
+classImplements_has_run = False
 
 
-@hookimpl(hookwrapper=True)
-def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
+@hookimpl(wrapper=True)
+def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
     if isinstance(item, TestCaseFunction) and "twisted.trial.unittest" in sys.modules:
         ut: Any = sys.modules["twisted.python.failure"]
+        global classImplements_has_run
         Failure__init__ = ut.Failure.__init__
-        check_testcase_implements_trial_reporter()
+        if not classImplements_has_run:
+            from twisted.trial.itrial import IReporter
+            from zope.interface import classImplements
+
+            classImplements(TestCaseFunction, IReporter)
+            classImplements_has_run = True
 
         def excstore(
             self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None
@@ -384,20 +419,13 @@ def excstore(
                 Failure__init__(self, exc_value, exc_type, exc_tb)
 
         ut.Failure.__init__ = excstore
-        yield
-        ut.Failure.__init__ = Failure__init__
+        try:
+            res = yield
+        finally:
+            ut.Failure.__init__ = Failure__init__
     else:
-        yield
-
-
-def check_testcase_implements_trial_reporter(done: List[int] = []) -> None:
-    if done:
-        return
-    from zope.interface import classImplements
-    from twisted.trial.itrial import IReporter
-
-    classImplements(TestCaseFunction, IReporter)
-    done.append(1)
+        res = yield
+    return res
 
 
 def _is_skipped(obj) -> bool:
diff --git a/src/_pytest/unraisableexception.py b/src/_pytest/unraisableexception.py
index fcb5d8237c1..7826aeccd12 100644
--- a/src/_pytest/unraisableexception.py
+++ b/src/_pytest/unraisableexception.py
@@ -1,93 +1,158 @@
+from __future__ import annotations
+
+import collections
+from collections.abc import Callable
+import functools
+import gc
 import sys
 import traceback
+from typing import NamedTuple
+from typing import TYPE_CHECKING
 import warnings
-from types import TracebackType
-from typing import Any
-from typing import Callable
-from typing import Generator
-from typing import Optional
-from typing import Type
 
+from _pytest.config import Config
+from _pytest.nodes import Item
+from _pytest.stash import StashKey
+from _pytest.tracemalloc import tracemalloc_message
 import pytest
 
 
-# Copied from cpython/Lib/test/support/__init__.py, with modifications.
-class catch_unraisable_exception:
-    """Context manager catching unraisable exception using sys.unraisablehook.
-
-    Storing the exception value (cm.unraisable.exc_value) creates a reference
-    cycle. The reference cycle is broken explicitly when the context manager
-    exits.
-
-    Storing the object (cm.unraisable.object) can resurrect it if it is set to
-    an object which is being finalized. Exiting the context manager clears the
-    stored object.
-
-    Usage:
-        with catch_unraisable_exception() as cm:
-            # code creating an "unraisable exception"
-            ...
-            # check the unraisable exception: use cm.unraisable
-            ...
-        # cm.unraisable attribute no longer exists at this point
-        # (to break a reference cycle)
-    """
-
-    def __init__(self) -> None:
-        self.unraisable: Optional["sys.UnraisableHookArgs"] = None
-        self._old_hook: Optional[Callable[["sys.UnraisableHookArgs"], Any]] = None
-
-    def _hook(self, unraisable: "sys.UnraisableHookArgs") -> None:
-        # Storing unraisable.object can resurrect an object which is being
-        # finalized. Storing unraisable.exc_value creates a reference cycle.
-        self.unraisable = unraisable
-
-    def __enter__(self) -> "catch_unraisable_exception":
-        self._old_hook = sys.unraisablehook
-        sys.unraisablehook = self._hook
-        return self
-
-    def __exit__(
-        self,
-        exc_type: Optional[Type[BaseException]],
-        exc_val: Optional[BaseException],
-        exc_tb: Optional[TracebackType],
-    ) -> None:
-        assert self._old_hook is not None
-        sys.unraisablehook = self._old_hook
-        self._old_hook = None
-        del self.unraisable
-
-
-def unraisable_exception_runtest_hook() -> Generator[None, None, None]:
-    with catch_unraisable_exception() as cm:
-        yield
-        if cm.unraisable:
-            if cm.unraisable.err_msg is not None:
-                err_msg = cm.unraisable.err_msg
-            else:
-                err_msg = "Exception ignored in"
-            msg = f"{err_msg}: {cm.unraisable.object!r}\n\n"
-            msg += "".join(
-                traceback.format_exception(
-                    cm.unraisable.exc_type,
-                    cm.unraisable.exc_value,
-                    cm.unraisable.exc_traceback,
-                )
+if TYPE_CHECKING:
+    pass
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import ExceptionGroup
+
+
+def gc_collect_harder() -> None:
+    # A single collection doesn't necessarily collect everything.
+    # Constant determined experimentally by the Trio project.
+    for _ in range(5):
+        gc.collect()
+
+
+class UnraisableMeta(NamedTuple):
+    msg: str
+    cause_msg: str
+    exc_value: BaseException | None
+
+
+unraisable_exceptions: StashKey[collections.deque[UnraisableMeta | BaseException]] = (
+    StashKey()
+)
+
+
+def collect_unraisable(config: Config) -> None:
+    pop_unraisable = config.stash[unraisable_exceptions].pop
+    errors: list[pytest.PytestUnraisableExceptionWarning | RuntimeError] = []
+    meta = None
+    hook_error = None
+    try:
+        while True:
+            try:
+                meta = pop_unraisable()
+            except IndexError:
+                break
+
+            if isinstance(meta, BaseException):
+                hook_error = RuntimeError("Failed to process unraisable exception")
+                hook_error.__cause__ = meta
+                errors.append(hook_error)
+                continue
+
+            msg = meta.msg
+            try:
+                warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
+            except pytest.PytestUnraisableExceptionWarning as e:
+                # This except happens when the warning is treated as an error (e.g. `-Werror`).
+                if meta.exc_value is not None:
+                    # Exceptions have a better way to show the traceback, but
+                    # warnings do not, so hide the traceback from the msg and
+                    # set the cause so the traceback shows up in the right place.
+                    e.args = (meta.cause_msg,)
+                    e.__cause__ = meta.exc_value
+                errors.append(e)
+
+        if len(errors) == 1:
+            raise errors[0]
+        if errors:
+            raise ExceptionGroup("multiple unraisable exception warnings", errors)
+    finally:
+        del errors, meta, hook_error
+
+
+def cleanup(
+    *, config: Config, prev_hook: Callable[[sys.UnraisableHookArgs], object]
+) -> None:
+    try:
+        try:
+            gc_collect_harder()
+            collect_unraisable(config)
+        finally:
+            sys.unraisablehook = prev_hook
+    finally:
+        del config.stash[unraisable_exceptions]
+
+
+def unraisable_hook(
+    unraisable: sys.UnraisableHookArgs,
+    /,
+    *,
+    append: Callable[[UnraisableMeta | BaseException], object],
+) -> None:
+    try:
+        # we need to compute these strings here as they might change after
+        # the unraisablehook finishes and before the metadata object is
+        # collected by a pytest hook
+        err_msg = (
+            "Exception ignored in" if unraisable.err_msg is None else unraisable.err_msg
+        )
+        summary = f"{err_msg}: {unraisable.object!r}"
+        traceback_message = "\n\n" + "".join(
+            traceback.format_exception(
+                unraisable.exc_type,
+                unraisable.exc_value,
+                unraisable.exc_traceback,
+            )
+        )
+        tracemalloc_tb = "\n" + tracemalloc_message(unraisable.object)
+        msg = summary + traceback_message + tracemalloc_tb
+        cause_msg = summary + tracemalloc_tb
+
+        append(
+            UnraisableMeta(
+                msg=msg,
+                cause_msg=cause_msg,
+                exc_value=unraisable.exc_value,
             )
-            warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))
+        )
+    except BaseException as e:
+        append(e)
+        # Raising this will cause the exception to be logged twice, once in our
+        # collect_unraisable and once by the unraisablehook calling machinery
+        # which is fine - this should never happen anyway and if it does
+        # it should probably be reported as a pytest bug.
+        raise
+
+
+def pytest_configure(config: Config) -> None:
+    prev_hook = sys.unraisablehook
+    deque: collections.deque[UnraisableMeta | BaseException] = collections.deque()
+    config.stash[unraisable_exceptions] = deque
+    config.add_cleanup(functools.partial(cleanup, config=config, prev_hook=prev_hook))
+    sys.unraisablehook = functools.partial(unraisable_hook, append=deque.append)
 
 
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_runtest_setup() -> Generator[None, None, None]:
-    yield from unraisable_exception_runtest_hook()
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_setup(item: Item) -> None:
+    collect_unraisable(item.config)
 
 
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_runtest_call() -> Generator[None, None, None]:
-    yield from unraisable_exception_runtest_hook()
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_call(item: Item) -> None:
+    collect_unraisable(item.config)
 
 
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_runtest_teardown() -> Generator[None, None, None]:
-    yield from unraisable_exception_runtest_hook()
+@pytest.hookimpl(trylast=True)
+def pytest_runtest_teardown(item: Item) -> None:
+    collect_unraisable(item.config)
diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py
index 2a97a319789..8c9ff2d9a36 100644
--- a/src/_pytest/warning_types.py
+++ b/src/_pytest/warning_types.py
@@ -1,11 +1,13 @@
+from __future__ import annotations
+
+import dataclasses
+import inspect
+from types import FunctionType
 from typing import Any
+from typing import final
 from typing import Generic
-from typing import Type
 from typing import TypeVar
-
-import attr
-
-from _pytest.compat import final
+import warnings
 
 
 class PytestWarning(UserWarning):
@@ -48,16 +50,8 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning):
     __module__ = "pytest"
 
 
-@final
-class PytestRemovedIn7Warning(PytestDeprecationWarning):
-    """Warning class for features that will be removed in pytest 7."""
-
-    __module__ = "pytest"
-
-
-@final
-class PytestRemovedIn8Warning(PytestDeprecationWarning):
-    """Warning class for features that will be removed in pytest 8."""
+class PytestRemovedIn9Warning(PytestDeprecationWarning):
+    """Warning class for features that will be removed in pytest 9."""
 
     __module__ = "pytest"
 
@@ -73,24 +67,8 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
     __module__ = "pytest"
 
     @classmethod
-    def simple(cls, apiname: str) -> "PytestExperimentalApiWarning":
-        return cls(
-            "{apiname} is an experimental api that may change over time".format(
-                apiname=apiname
-            )
-        )
-
-
-@final
-class PytestUnhandledCoroutineWarning(PytestWarning):
-    """Warning emitted for an unhandled coroutine.
-
-    A coroutine was encountered when collecting test functions, but was not
-    handled by any async-aware plugin.
-    Coroutine test functions are not natively supported.
-    """
-
-    __module__ = "pytest"
+    def simple(cls, apiname: str) -> PytestExperimentalApiWarning:
+        return cls(f"{apiname} is an experimental api that may change over time")
 
 
 @final
@@ -129,7 +107,7 @@ class PytestUnhandledThreadExceptionWarning(PytestWarning):
 
 
 @final
-@attr.s(auto_attribs=True)
+@dataclasses.dataclass
 class UnformattedWarning(Generic[_W]):
     """A warning meant to be formatted during runtime.
 
@@ -137,9 +115,41 @@ class UnformattedWarning(Generic[_W]):
     as opposed to a direct message.
     """
 
-    category: Type["_W"]
+    category: type[_W]
     template: str
 
     def format(self, **kwargs: Any) -> _W:
         """Return an instance of the warning category, formatted with given kwargs."""
         return self.category(self.template.format(**kwargs))
+
+
+@final
+class PytestFDWarning(PytestWarning):
+    """When the lsof plugin finds leaked fds."""
+
+    __module__ = "pytest"
+
+
+def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None:
+    """
+    Issue the warning :param:`message` for the definition of the given :param:`method`
+
+    this helps to log warnings for functions defined prior to finding an issue with them
+    (like hook wrappers being marked in a legacy mechanism)
+    """
+    lineno = method.__code__.co_firstlineno
+    filename = inspect.getfile(method)
+    module = method.__module__
+    mod_globals = method.__globals__
+    try:
+        warnings.warn_explicit(
+            message,
+            type(message),
+            filename=filename,
+            module=module,
+            registry=mod_globals.setdefault("__warningregistry__", {}),
+            lineno=lineno,
+        )
+    except Warning as w:
+        # If warnings are errors (e.g. -Werror), location information gets lost, so we add it to the message.
+        raise type(w)(f"{w}\n at {filename}:{lineno}") from None
diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py
index c0c946cbde5..806681a5020 100644
--- a/src/_pytest/warnings.py
+++ b/src/_pytest/warnings.py
@@ -1,37 +1,32 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Generator
+from contextlib import contextmanager
+from contextlib import ExitStack
 import sys
+from typing import Literal
 import warnings
-from contextlib import contextmanager
-from typing import Generator
-from typing import Optional
-from typing import TYPE_CHECKING
 
-import pytest
 from _pytest.config import apply_warning_filters
 from _pytest.config import Config
 from _pytest.config import parse_warning_filter
 from _pytest.main import Session
 from _pytest.nodes import Item
 from _pytest.terminal import TerminalReporter
-
-if TYPE_CHECKING:
-    from typing_extensions import Literal
-
-
-def pytest_configure(config: Config) -> None:
-    config.addinivalue_line(
-        "markers",
-        "filterwarnings(warning): add a warning filter to the given test. "
-        "see https://docs.pytest.org/en/stable/how-to/capture-warnings.html#pytest-mark-filterwarnings ",
-    )
+from _pytest.tracemalloc import tracemalloc_message
+import pytest
 
 
 @contextmanager
 def catch_warnings_for_item(
     config: Config,
     ihook,
-    when: "Literal['config', 'collect', 'runtest']",
-    item: Optional[Item],
-) -> Generator[None, None, None]:
+    when: Literal["config", "collect", "runtest"],
+    item: Item | None,
+    *,
+    record: bool = True,
+) -> Generator[None]:
     """Context manager that catches warnings generated in the contained execution block.
 
     ``item`` can be None if we are not in the context of an item execution.
@@ -40,16 +35,14 @@ def catch_warnings_for_item(
     """
     config_filters = config.getini("filterwarnings")
     cmdline_filters = config.known_args_namespace.pythonwarnings or []
-    with warnings.catch_warnings(record=True) as log:
-        # mypy can't infer that record=True means log is not None; help it.
-        assert log is not None
-
+    with warnings.catch_warnings(record=record) as log:
         if not sys.warnoptions:
             # If user is not explicitly configuring warning filters, show deprecation warnings by default (#2908).
             warnings.filterwarnings("always", category=DeprecationWarning)
             warnings.filterwarnings("always", category=PendingDeprecationWarning)
 
-        warnings.filterwarnings("error", category=pytest.PytestRemovedIn7Warning)
+        # To be enabled in pytest 9.0.0.
+        # warnings.filterwarnings("error", category=pytest.PytestRemovedIn9Warning)
 
         apply_warning_filters(config_filters, cmdline_filters)
 
@@ -60,82 +53,100 @@ def catch_warnings_for_item(
                 for arg in mark.args:
                     warnings.filterwarnings(*parse_warning_filter(arg, escape=False))
 
-        yield
+        try:
+            yield
+        finally:
+            if record:
+                # mypy can't infer that record=True means log is not None; help it.
+                assert log is not None
 
-        for warning_message in log:
-            ihook.pytest_warning_captured.call_historic(
-                kwargs=dict(
-                    warning_message=warning_message,
-                    when=when,
-                    item=item,
-                    location=None,
-                )
-            )
-            ihook.pytest_warning_recorded.call_historic(
-                kwargs=dict(
-                    warning_message=warning_message,
-                    nodeid=nodeid,
-                    when=when,
-                    location=None,
-                )
-            )
+                for warning_message in log:
+                    ihook.pytest_warning_recorded.call_historic(
+                        kwargs=dict(
+                            warning_message=warning_message,
+                            nodeid=nodeid,
+                            when=when,
+                            location=None,
+                        )
+                    )
 
 
 def warning_record_to_str(warning_message: warnings.WarningMessage) -> str:
     """Convert a warnings.WarningMessage to a string."""
-    warn_msg = warning_message.message
-    msg = warnings.formatwarning(
-        str(warn_msg),
+    return warnings.formatwarning(
+        str(warning_message.message),
         warning_message.category,
         warning_message.filename,
         warning_message.lineno,
         warning_message.line,
-    )
-    return msg
+    ) + tracemalloc_message(warning_message.source)
 
 
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
+@pytest.hookimpl(wrapper=True, tryfirst=True)
+def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]:
     with catch_warnings_for_item(
         config=item.config, ihook=item.ihook, when="runtest", item=item
     ):
-        yield
+        return (yield)
 
 
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_collection(session: Session) -> Generator[None, None, None]:
+@pytest.hookimpl(wrapper=True, tryfirst=True)
+def pytest_collection(session: Session) -> Generator[None, object, object]:
     config = session.config
     with catch_warnings_for_item(
         config=config, ihook=config.hook, when="collect", item=None
     ):
-        yield
+        return (yield)
 
 
-@pytest.hookimpl(hookwrapper=True)
+@pytest.hookimpl(wrapper=True)
 def pytest_terminal_summary(
     terminalreporter: TerminalReporter,
-) -> Generator[None, None, None]:
+) -> Generator[None]:
     config = terminalreporter.config
     with catch_warnings_for_item(
         config=config, ihook=config.hook, when="config", item=None
     ):
-        yield
+        return (yield)
 
 
-@pytest.hookimpl(hookwrapper=True)
-def pytest_sessionfinish(session: Session) -> Generator[None, None, None]:
+@pytest.hookimpl(wrapper=True)
+def pytest_sessionfinish(session: Session) -> Generator[None]:
     config = session.config
     with catch_warnings_for_item(
         config=config, ihook=config.hook, when="config", item=None
     ):
-        yield
+        return (yield)
 
 
-@pytest.hookimpl(hookwrapper=True)
+@pytest.hookimpl(wrapper=True)
 def pytest_load_initial_conftests(
-    early_config: "Config",
-) -> Generator[None, None, None]:
+    early_config: Config,
+) -> Generator[None]:
     with catch_warnings_for_item(
         config=early_config, ihook=early_config.hook, when="config", item=None
     ):
-        yield
+        return (yield)
+
+
+def pytest_configure(config: Config) -> None:
+    with ExitStack() as stack:
+        stack.enter_context(
+            catch_warnings_for_item(
+                config=config,
+                ihook=config.hook,
+                when="config",
+                item=None,
+                # this disables recording because the terminalreporter has
+                # finished by the time it comes to reporting logged warnings
+                # from the end of config cleanup. So for now, this is only
+                # useful for setting a warning filter with an 'error' action.
+                record=False,
+            )
+        )
+        config.addinivalue_line(
+            "markers",
+            "filterwarnings(warning): add a warning filter to the given test. "
+            "see https://docs.pytest.org/en/stable/how-to/capture-warnings.html#pytest-mark-filterwarnings ",
+        )
+        config.add_cleanup(stack.pop_all().close)
diff --git a/src/py.py b/src/py.py
new file mode 100644
index 00000000000..5c661e66c1f
--- /dev/null
+++ b/src/py.py
@@ -0,0 +1,15 @@
+# shim for pylib going away
+# if pylib is installed this file will get skipped
+# (`py/__init__.py` has higher precedence)
+from __future__ import annotations
+
+import sys
+
+import _pytest._py.error as error
+import _pytest._py.path as path
+
+
+sys.modules["py.error"] = error
+sys.modules["py.path"] = path
+
+__all__ = ["error", "path"]
diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py
index 7a596cd3783..e36d3e704c1 100644
--- a/src/pytest/__init__.py
+++ b/src/pytest/__init__.py
@@ -1,6 +1,8 @@
 # PYTHON_ARGCOMPLETE_OK
 """pytest: unit and functional testing with Python."""
-from . import collect
+
+from __future__ import annotations
+
 from _pytest import __version__
 from _pytest import version_tuple
 from _pytest._code import ExceptionInfo
@@ -19,14 +21,19 @@
 from _pytest.config.argparsing import OptionGroup
 from _pytest.config.argparsing import Parser
 from _pytest.debugging import pytestPDB as __pytestPDB
-from _pytest.fixtures import _fillfuncargs
+from _pytest.doctest import DoctestItem
 from _pytest.fixtures import fixture
+from _pytest.fixtures import FixtureDef
 from _pytest.fixtures import FixtureLookupError
 from _pytest.fixtures import FixtureRequest
 from _pytest.fixtures import yield_fixture
 from _pytest.freeze_support import freeze_includes
+from _pytest.legacypath import TempdirFactory
+from _pytest.legacypath import Testdir
 from _pytest.logging import LogCaptureFixture
+from _pytest.main import Dir
 from _pytest.main import Session
+from _pytest.mark import HIDDEN_PARAM
 from _pytest.mark import Mark
 from _pytest.mark import MARK_GEN as mark
 from _pytest.mark import MarkDecorator
@@ -34,6 +41,7 @@
 from _pytest.mark import param
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.nodes import Collector
+from _pytest.nodes import Directory
 from _pytest.nodes import File
 from _pytest.nodes import Item
 from _pytest.outcomes import exit
@@ -52,7 +60,9 @@
 from _pytest.python import Module
 from _pytest.python import Package
 from _pytest.python_api import approx
-from _pytest.python_api import raises
+from _pytest.raises import raises
+from _pytest.raises import RaisesExc
+from _pytest.raises import RaisesGroup
 from _pytest.recwarn import deprecated_call
 from _pytest.recwarn import WarningsRecorder
 from _pytest.recwarn import warns
@@ -61,6 +71,8 @@
 from _pytest.runner import CallInfo
 from _pytest.stash import Stash
 from _pytest.stash import StashKey
+from _pytest.terminal import TerminalReporter
+from _pytest.terminal import TestShortLogReport
 from _pytest.tmpdir import TempPathFactory
 from _pytest.warning_types import PytestAssertRewriteWarning
 from _pytest.warning_types import PytestCacheWarning
@@ -68,51 +80,40 @@
 from _pytest.warning_types import PytestConfigWarning
 from _pytest.warning_types import PytestDeprecationWarning
 from _pytest.warning_types import PytestExperimentalApiWarning
-from _pytest.warning_types import PytestRemovedIn7Warning
-from _pytest.warning_types import PytestRemovedIn8Warning
-from _pytest.warning_types import PytestUnhandledCoroutineWarning
+from _pytest.warning_types import PytestFDWarning
+from _pytest.warning_types import PytestRemovedIn9Warning
 from _pytest.warning_types import PytestUnhandledThreadExceptionWarning
 from _pytest.warning_types import PytestUnknownMarkWarning
 from _pytest.warning_types import PytestUnraisableExceptionWarning
 from _pytest.warning_types import PytestWarning
 
+
 set_trace = __pytestPDB.set_trace
 
 
 __all__ = [
-    "__version__",
-    "_fillfuncargs",
-    "approx",
+    "HIDDEN_PARAM",
     "Cache",
     "CallInfo",
     "CaptureFixture",
     "Class",
-    "cmdline",
-    "collect",
-    "Collector",
     "CollectReport",
+    "Collector",
     "Config",
-    "console_main",
-    "deprecated_call",
-    "exit",
+    "Dir",
+    "Directory",
+    "DoctestItem",
     "ExceptionInfo",
     "ExitCode",
-    "fail",
     "File",
-    "fixture",
+    "FixtureDef",
     "FixtureLookupError",
     "FixtureRequest",
-    "freeze_includes",
     "Function",
-    "hookimpl",
     "HookRecorder",
-    "hookspec",
-    "importorskip",
     "Item",
     "LineMatcher",
     "LogCaptureFixture",
-    "main",
-    "mark",
     "Mark",
     "MarkDecorator",
     "MarkGenerator",
@@ -121,7 +122,6 @@
     "MonkeyPatch",
     "OptionGroup",
     "Package",
-    "param",
     "Parser",
     "PytestAssertRewriteWarning",
     "PytestCacheWarning",
@@ -129,39 +129,50 @@
     "PytestConfigWarning",
     "PytestDeprecationWarning",
     "PytestExperimentalApiWarning",
-    "PytestRemovedIn7Warning",
-    "PytestRemovedIn8Warning",
-    "Pytester",
+    "PytestFDWarning",
     "PytestPluginManager",
-    "PytestUnhandledCoroutineWarning",
+    "PytestRemovedIn9Warning",
     "PytestUnhandledThreadExceptionWarning",
     "PytestUnknownMarkWarning",
     "PytestUnraisableExceptionWarning",
     "PytestWarning",
-    "raises",
+    "Pytester",
+    "RaisesExc",
+    "RaisesGroup",
     "RecordedHookCall",
-    "register_assert_rewrite",
     "RunResult",
     "Session",
-    "set_trace",
-    "skip",
     "Stash",
     "StashKey",
-    "version_tuple",
     "TempPathFactory",
+    "TempdirFactory",
+    "TerminalReporter",
     "TestReport",
+    "TestShortLogReport",
+    "Testdir",
     "UsageError",
     "WarningsRecorder",
+    "__version__",
+    "approx",
+    "cmdline",
+    "console_main",
+    "deprecated_call",
+    "exit",
+    "fail",
+    "fixture",
+    "freeze_includes",
+    "hookimpl",
+    "hookspec",
+    "importorskip",
+    "main",
+    "mark",
+    "param",
+    "raises",
+    "register_assert_rewrite",
+    "set_trace",
+    "skip",
+    "version_tuple",
     "warns",
     "xfail",
     "yield_fixture",
 ]
-
-
-def __getattr__(name: str) -> object:
-    if name == "Instance":
-        # The import emits a deprecation warning.
-        from _pytest.python import Instance
-
-        return Instance
-    raise AttributeError(f"module {__name__} has no attribute {name}")
diff --git a/src/pytest/__main__.py b/src/pytest/__main__.py
index b170152937b..cccab5d57b8 100644
--- a/src/pytest/__main__.py
+++ b/src/pytest/__main__.py
@@ -1,5 +1,9 @@
 """The pytest entry point."""
+
+from __future__ import annotations
+
 import pytest
 
+
 if __name__ == "__main__":
     raise SystemExit(pytest.console_main())
diff --git a/src/pytest/collect.py b/src/pytest/collect.py
deleted file mode 100644
index 4b2b5818066..00000000000
--- a/src/pytest/collect.py
+++ /dev/null
@@ -1,38 +0,0 @@
-import sys
-import warnings
-from types import ModuleType
-from typing import Any
-from typing import List
-
-import pytest
-from _pytest.deprecated import PYTEST_COLLECT_MODULE
-
-COLLECT_FAKEMODULE_ATTRIBUTES = [
-    "Collector",
-    "Module",
-    "Function",
-    "Session",
-    "Item",
-    "Class",
-    "File",
-    "_fillfuncargs",
-]
-
-
-class FakeCollectModule(ModuleType):
-    def __init__(self) -> None:
-        super().__init__("pytest.collect")
-        self.__all__ = list(COLLECT_FAKEMODULE_ATTRIBUTES)
-        self.__pytest = pytest
-
-    def __dir__(self) -> List[str]:
-        return dir(super()) + self.__all__
-
-    def __getattr__(self, name: str) -> Any:
-        if name not in self.__all__:
-            raise AttributeError(name)
-        warnings.warn(PYTEST_COLLECT_MODULE.format(name=name), stacklevel=2)
-        return getattr(pytest, name)
-
-
-sys.modules["pytest.collect"] = FakeCollectModule()
diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py
new file mode 100644
index 00000000000..03a828c64f0
--- /dev/null
+++ b/testing/_py/test_local.py
@@ -0,0 +1,1583 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+import contextlib
+import multiprocessing
+import os
+import sys
+import time
+from unittest import mock
+import warnings
+
+from py import error
+from py.path import local
+
+import pytest
+
+
+@contextlib.contextmanager
+def ignore_encoding_warning():
+    with warnings.catch_warnings():
+        if sys.version_info >= (3, 10):
+            warnings.simplefilter("ignore", EncodingWarning)  # noqa: F821
+        yield
+
+
+class CommonFSTests:
+    def test_constructor_equality(self, path1):
+        p = path1.__class__(path1)
+        assert p == path1
+
+    def test_eq_nonstring(self, path1):
+        p1 = path1.join("sampledir")
+        p2 = path1.join("sampledir")
+        assert p1 == p2
+
+    def test_new_identical(self, path1):
+        assert path1 == path1.new()
+
+    def test_join(self, path1):
+        p = path1.join("sampledir")
+        strp = str(p)
+        assert strp.endswith("sampledir")
+        assert strp.startswith(str(path1))
+
+    def test_join_normalized(self, path1):
+        newpath = path1.join(path1.sep + "sampledir")
+        strp = str(newpath)
+        assert strp.endswith("sampledir")
+        assert strp.startswith(str(path1))
+        newpath = path1.join((path1.sep * 2) + "sampledir")
+        strp = str(newpath)
+        assert strp.endswith("sampledir")
+        assert strp.startswith(str(path1))
+
+    def test_join_noargs(self, path1):
+        newpath = path1.join()
+        assert path1 == newpath
+
+    def test_add_something(self, path1):
+        p = path1.join("sample")
+        p = p + "dir"
+        assert p.check()
+        assert p.exists()
+        assert p.isdir()
+        assert not p.isfile()
+
+    def test_parts(self, path1):
+        newpath = path1.join("sampledir", "otherfile")
+        par = newpath.parts()[-3:]
+        assert par == [path1, path1.join("sampledir"), newpath]
+
+        revpar = newpath.parts(reverse=True)[:3]
+        assert revpar == [newpath, path1.join("sampledir"), path1]
+
+    def test_common(self, path1):
+        other = path1.join("sampledir")
+        x = other.common(path1)
+        assert x == path1
+
+    # def test_parents_nonexisting_file(self, path1):
+    #    newpath = path1 / 'dirnoexist' / 'nonexisting file'
+    #    par = list(newpath.parents())
+    #    assert par[:2] == [path1 / 'dirnoexist', path1]
+
+    def test_basename_checks(self, path1):
+        newpath = path1.join("sampledir")
+        assert newpath.check(basename="sampledir")
+        assert newpath.check(notbasename="xyz")
+        assert newpath.basename == "sampledir"
+
+    def test_basename(self, path1):
+        newpath = path1.join("sampledir")
+        assert newpath.check(basename="sampledir")
+        assert newpath.basename, "sampledir"
+
+    def test_dirname(self, path1):
+        newpath = path1.join("sampledir")
+        assert newpath.dirname == str(path1)
+
+    def test_dirpath(self, path1):
+        newpath = path1.join("sampledir")
+        assert newpath.dirpath() == path1
+
+    def test_dirpath_with_args(self, path1):
+        newpath = path1.join("sampledir")
+        assert newpath.dirpath("x") == path1.join("x")
+
+    def test_newbasename(self, path1):
+        newpath = path1.join("samplefile")
+        newbase = newpath.new(basename="samplefile2")
+        assert newbase.basename == "samplefile2"
+        assert newbase.dirpath() == newpath.dirpath()
+
+    def test_not_exists(self, path1):
+        assert not path1.join("does_not_exist").check()
+        assert path1.join("does_not_exist").check(exists=0)
+
+    def test_exists(self, path1):
+        assert path1.join("samplefile").check()
+        assert path1.join("samplefile").check(exists=1)
+        assert path1.join("samplefile").exists()
+        assert path1.join("samplefile").isfile()
+        assert not path1.join("samplefile").isdir()
+
+    def test_dir(self, path1):
+        # print repr(path1.join("sampledir"))
+        assert path1.join("sampledir").check(dir=1)
+        assert path1.join("samplefile").check(notdir=1)
+        assert not path1.join("samplefile").check(dir=1)
+        assert path1.join("samplefile").exists()
+        assert not path1.join("samplefile").isdir()
+        assert path1.join("samplefile").isfile()
+
+    def test_fnmatch_file(self, path1):
+        assert path1.join("samplefile").check(fnmatch="s*e")
+        assert path1.join("samplefile").fnmatch("s*e")
+        assert not path1.join("samplefile").fnmatch("s*x")
+        assert not path1.join("samplefile").check(fnmatch="s*x")
+
+    # def test_fnmatch_dir(self, path1):
+
+    #    pattern = path1.sep.join(['s*file'])
+    #    sfile = path1.join("samplefile")
+    #    assert sfile.check(fnmatch=pattern)
+
+    def test_relto(self, path1):
+        p = path1.join("sampledir", "otherfile")
+        assert p.relto(path1) == p.sep.join(["sampledir", "otherfile"])
+        assert p.check(relto=path1)
+        assert path1.check(notrelto=p)
+        assert not path1.check(relto=p)
+
+    def test_bestrelpath(self, path1):
+        curdir = path1
+        sep = curdir.sep
+        s = curdir.bestrelpath(curdir)
+        assert s == "."
+        s = curdir.bestrelpath(curdir.join("hello", "world"))
+        assert s == "hello" + sep + "world"
+
+        s = curdir.bestrelpath(curdir.dirpath().join("sister"))
+        assert s == ".." + sep + "sister"
+        assert curdir.bestrelpath(curdir.dirpath()) == ".."
+
+        assert curdir.bestrelpath("hello") == "hello"
+
+    def test_relto_not_relative(self, path1):
+        l1 = path1.join("bcde")
+        l2 = path1.join("b")
+        assert not l1.relto(l2)
+        assert not l2.relto(l1)
+
+    def test_listdir(self, path1):
+        p = path1.listdir()
+        assert path1.join("sampledir") in p
+        assert path1.join("samplefile") in p
+        with pytest.raises(error.ENOTDIR):
+            path1.join("samplefile").listdir()
+
+    def test_listdir_fnmatchstring(self, path1):
+        p = path1.listdir("s*dir")
+        assert len(p)
+        assert p[0], path1.join("sampledir")
+
+    def test_listdir_filter(self, path1):
+        p = path1.listdir(lambda x: x.check(dir=1))
+        assert path1.join("sampledir") in p
+        assert path1.join("samplefile") not in p
+
+    def test_listdir_sorted(self, path1):
+        p = path1.listdir(lambda x: x.check(basestarts="sample"), sort=True)
+        assert path1.join("sampledir") == p[0]
+        assert path1.join("samplefile") == p[1]
+        assert path1.join("samplepickle") == p[2]
+
+    def test_visit_nofilter(self, path1):
+        lst = []
+        for i in path1.visit():
+            lst.append(i.relto(path1))
+        assert "sampledir" in lst
+        assert path1.sep.join(["sampledir", "otherfile"]) in lst
+
+    def test_visit_norecurse(self, path1):
+        lst = []
+        for i in path1.visit(None, lambda x: x.basename != "sampledir"):
+            lst.append(i.relto(path1))
+        assert "sampledir" in lst
+        assert path1.sep.join(["sampledir", "otherfile"]) not in lst
+
+    @pytest.mark.parametrize(
+        "fil",
+        ["*dir", "*dir", pytest.mark.skip("sys.version_info < (3,6)")(b"*dir")],
+    )
+    def test_visit_filterfunc_is_string(self, path1, fil):
+        lst = []
+        for i in path1.visit(fil):
+            lst.append(i.relto(path1))
+        assert len(lst), 2  # noqa: PLC1802,RUF040
+        assert "sampledir" in lst
+        assert "otherdir" in lst
+
+    def test_visit_ignore(self, path1):
+        p = path1.join("nonexisting")
+        assert list(p.visit(ignore=error.ENOENT)) == []
+
+    def test_visit_endswith(self, path1):
+        p = []
+        for i in path1.visit(lambda x: x.check(endswith="file")):
+            p.append(i.relto(path1))
+        assert path1.sep.join(["sampledir", "otherfile"]) in p
+        assert "samplefile" in p
+
+    def test_cmp(self, path1):
+        path1 = path1.join("samplefile")
+        path2 = path1.join("samplefile2")
+        assert (path1 < path2) == ("samplefile" < "samplefile2")
+        assert not (path1 < path1)
+
+    def test_simple_read(self, path1):
+        with ignore_encoding_warning():
+            x = path1.join("samplefile").read("r")
+        assert x == "samplefile\n"
+
+    def test_join_div_operator(self, path1):
+        newpath = path1 / "/sampledir" / "/test//"
+        newpath2 = path1.join("sampledir", "test")
+        assert newpath == newpath2
+
+    def test_ext(self, path1):
+        newpath = path1.join("sampledir.ext")
+        assert newpath.ext == ".ext"
+        newpath = path1.join("sampledir")
+        assert not newpath.ext
+
+    def test_purebasename(self, path1):
+        newpath = path1.join("samplefile.py")
+        assert newpath.purebasename == "samplefile"
+
+    def test_multiple_parts(self, path1):
+        newpath = path1.join("samplefile.py")
+        dirname, purebasename, basename, ext = newpath._getbyspec(
+            "dirname,purebasename,basename,ext"
+        )
+        assert str(path1).endswith(dirname)  # be careful with win32 'drive'
+        assert purebasename == "samplefile"
+        assert basename == "samplefile.py"
+        assert ext == ".py"
+
+    def test_dotted_name_ext(self, path1):
+        newpath = path1.join("a.b.c")
+        ext = newpath.ext
+        assert ext == ".c"
+        assert newpath.ext == ".c"
+
+    def test_newext(self, path1):
+        newpath = path1.join("samplefile.py")
+        newext = newpath.new(ext=".txt")
+        assert newext.basename == "samplefile.txt"
+        assert newext.purebasename == "samplefile"
+
+    def test_readlines(self, path1):
+        fn = path1.join("samplefile")
+        with ignore_encoding_warning():
+            contents = fn.readlines()
+        assert contents == ["samplefile\n"]
+
+    def test_readlines_nocr(self, path1):
+        fn = path1.join("samplefile")
+        with ignore_encoding_warning():
+            contents = fn.readlines(cr=0)
+        assert contents == ["samplefile", ""]
+
+    def test_file(self, path1):
+        assert path1.join("samplefile").check(file=1)
+
+    def test_not_file(self, path1):
+        assert not path1.join("sampledir").check(file=1)
+        assert path1.join("sampledir").check(file=0)
+
+    def test_non_existent(self, path1):
+        assert path1.join("sampledir.nothere").check(dir=0)
+        assert path1.join("sampledir.nothere").check(file=0)
+        assert path1.join("sampledir.nothere").check(notfile=1)
+        assert path1.join("sampledir.nothere").check(notdir=1)
+        assert path1.join("sampledir.nothere").check(notexists=1)
+        assert not path1.join("sampledir.nothere").check(notfile=0)
+
+    #    pattern = path1.sep.join(['s*file'])
+    #    sfile = path1.join("samplefile")
+    #    assert sfile.check(fnmatch=pattern)
+
+    def test_size(self, path1):
+        url = path1.join("samplefile")
+        assert url.size() > len("samplefile")
+
+    def test_mtime(self, path1):
+        url = path1.join("samplefile")
+        assert url.mtime() > 0
+
+    def test_relto_wrong_type(self, path1):
+        with pytest.raises(TypeError):
+            path1.relto(42)
+
+    def test_load(self, path1):
+        p = path1.join("samplepickle")
+        obj = p.load()
+        assert type(obj) is dict
+        assert obj.get("answer", None) == 42
+
+    def test_visit_filesonly(self, path1):
+        p = []
+        for i in path1.visit(lambda x: x.check(file=1)):
+            p.append(i.relto(path1))
+        assert "sampledir" not in p
+        assert path1.sep.join(["sampledir", "otherfile"]) in p
+
+    def test_visit_nodotfiles(self, path1):
+        p = []
+        for i in path1.visit(lambda x: x.check(dotfile=0)):
+            p.append(i.relto(path1))
+        assert "sampledir" in p
+        assert path1.sep.join(["sampledir", "otherfile"]) in p
+        assert ".dotfile" not in p
+
+    def test_visit_breadthfirst(self, path1):
+        lst = []
+        for i in path1.visit(bf=True):
+            lst.append(i.relto(path1))
+        for i, p in enumerate(lst):
+            if path1.sep in p:
+                for j in range(i, len(lst)):
+                    assert path1.sep in lst[j]
+                break
+        else:
+            pytest.fail("huh")
+
+    def test_visit_sort(self, path1):
+        lst = []
+        for i in path1.visit(bf=True, sort=True):
+            lst.append(i.relto(path1))
+        for i, p in enumerate(lst):
+            if path1.sep in p:
+                break
+        assert lst[:i] == sorted(lst[:i])
+        assert lst[i:] == sorted(lst[i:])
+
+    def test_endswith(self, path1):
+        def chk(p):
+            return p.check(endswith="pickle")
+
+        assert not chk(path1)
+        assert not chk(path1.join("samplefile"))
+        assert chk(path1.join("somepickle"))
+
+    def test_copy_file(self, path1):
+        otherdir = path1.join("otherdir")
+        initpy = otherdir.join("__init__.py")
+        copied = otherdir.join("copied")
+        initpy.copy(copied)
+        try:
+            assert copied.check()
+            s1 = initpy.read_text(encoding="utf-8")
+            s2 = copied.read_text(encoding="utf-8")
+            assert s1 == s2
+        finally:
+            if copied.check():
+                copied.remove()
+
+    def test_copy_dir(self, path1):
+        otherdir = path1.join("otherdir")
+        copied = path1.join("newdir")
+        try:
+            otherdir.copy(copied)
+            assert copied.check(dir=1)
+            assert copied.join("__init__.py").check(file=1)
+            s1 = otherdir.join("__init__.py").read_text(encoding="utf-8")
+            s2 = copied.join("__init__.py").read_text(encoding="utf-8")
+            assert s1 == s2
+        finally:
+            if copied.check(dir=1):
+                copied.remove(rec=1)
+
+    def test_remove_file(self, path1):
+        d = path1.ensure("todeleted")
+        assert d.check()
+        d.remove()
+        assert not d.check()
+
+    def test_remove_dir_recursive_by_default(self, path1):
+        d = path1.ensure("to", "be", "deleted")
+        assert d.check()
+        p = path1.join("to")
+        p.remove()
+        assert not p.check()
+
+    def test_ensure_dir(self, path1):
+        b = path1.ensure_dir("001", "002")
+        assert b.basename == "002"
+        assert b.isdir()
+
+    def test_mkdir_and_remove(self, path1):
+        tmpdir = path1
+        with pytest.raises(error.EEXIST):
+            tmpdir.mkdir("sampledir")
+        new = tmpdir.join("mktest1")
+        new.mkdir()
+        assert new.check(dir=1)
+        new.remove()
+
+        new = tmpdir.mkdir("mktest")
+        assert new.check(dir=1)
+        new.remove()
+        assert tmpdir.join("mktest") == new
+
+    def test_move_file(self, path1):
+        p = path1.join("samplefile")
+        newp = p.dirpath("moved_samplefile")
+        p.move(newp)
+        try:
+            assert newp.check(file=1)
+            assert not p.check()
+        finally:
+            dp = newp.dirpath()
+            if hasattr(dp, "revert"):
+                dp.revert()
+            else:
+                newp.move(p)
+                assert p.check()
+
+    def test_move_dir(self, path1):
+        source = path1.join("sampledir")
+        dest = path1.join("moveddir")
+        source.move(dest)
+        assert dest.check(dir=1)
+        assert dest.join("otherfile").check(file=1)
+        assert not source.join("sampledir").check()
+
+    def test_fspath_protocol_match_strpath(self, path1):
+        assert path1.__fspath__() == path1.strpath
+
+    def test_fspath_func_match_strpath(self, path1):
+        from os import fspath
+
+        assert fspath(path1) == path1.strpath
+
+    @pytest.mark.skip("sys.version_info < (3,6)")
+    def test_fspath_open(self, path1):
+        f = path1.join("opentestfile")
+        open(f)
+
+    @pytest.mark.skip("sys.version_info < (3,6)")
+    def test_fspath_fsencode(self, path1):
+        from os import fsencode
+
+        assert fsencode(path1) == fsencode(path1.strpath)
+
+
+def setuptestfs(path):
+    if path.join("samplefile").check():
+        return
+    # print "setting up test fs for", repr(path)
+    samplefile = path.ensure("samplefile")
+    samplefile.write_text("samplefile\n", encoding="utf-8")
+
+    execfile = path.ensure("execfile")
+    execfile.write_text("x=42", encoding="utf-8")
+
+    execfilepy = path.ensure("execfile.py")
+    execfilepy.write_text("x=42", encoding="utf-8")
+
+    d = {1: 2, "hello": "world", "answer": 42}
+    path.ensure("samplepickle").dump(d)
+
+    sampledir = path.ensure("sampledir", dir=1)
+    sampledir.ensure("otherfile")
+
+    otherdir = path.ensure("otherdir", dir=1)
+    otherdir.ensure("__init__.py")
+
+    module_a = otherdir.ensure("a.py")
+    module_a.write_text("from .b import stuff as result\n", encoding="utf-8")
+    module_b = otherdir.ensure("b.py")
+    module_b.write_text('stuff="got it"\n', encoding="utf-8")
+    module_c = otherdir.ensure("c.py")
+    module_c.write_text(
+        """import py;
+import otherdir.a
+value = otherdir.a.result
+""",
+        encoding="utf-8",
+    )
+    module_d = otherdir.ensure("d.py")
+    module_d.write_text(
+        """import py;
+from otherdir import a
+value2 = a.result
+""",
+        encoding="utf-8",
+    )
+
+
+win32only = pytest.mark.skipif(
+    "not (sys.platform == 'win32' or getattr(os, '_name', None) == 'nt')"
+)
+skiponwin32 = pytest.mark.skipif(
+    "sys.platform == 'win32' or getattr(os, '_name', None) == 'nt'"
+)
+
+ATIME_RESOLUTION = 0.01
+
+
+@pytest.fixture(scope="session")
+def path1(tmpdir_factory):
+    path = tmpdir_factory.mktemp("path")
+    setuptestfs(path)
+    yield path
+    assert path.join("samplefile").check()
+
+
+@pytest.fixture
+def fake_fspath_obj(request):
+    class FakeFSPathClass:
+        def __init__(self, path):
+            self._path = path
+
+        def __fspath__(self):
+            return self._path
+
+    return FakeFSPathClass(os.path.join("this", "is", "a", "fake", "path"))
+
+
+def batch_make_numbered_dirs(rootdir, repeats):
+    for i in range(repeats):
+        dir_ = local.make_numbered_dir(prefix="repro-", rootdir=rootdir)
+        file_ = dir_.join("foo")
+        file_.write_text(f"{i}", encoding="utf-8")
+        actual = int(file_.read_text(encoding="utf-8"))
+        assert actual == i, (
+            f"int(file_.read_text(encoding='utf-8')) is {actual} instead of {i}"
+        )
+        dir_.join(".lock").remove(ignore_errors=True)
+    return True
+
+
+class TestLocalPath(CommonFSTests):
+    def test_join_normpath(self, tmpdir):
+        assert tmpdir.join(".") == tmpdir
+        p = tmpdir.join(f"../{tmpdir.basename}")
+        assert p == tmpdir
+        p = tmpdir.join(f"..//{tmpdir.basename}/")
+        assert p == tmpdir
+
+    @skiponwin32
+    def test_dirpath_abs_no_abs(self, tmpdir):
+        p = tmpdir.join("foo")
+        assert p.dirpath("/bar") == tmpdir.join("bar")
+        assert tmpdir.dirpath("/bar", abs=True) == local("/bar")
+
+    def test_gethash(self, tmpdir):
+        from hashlib import md5
+        from hashlib import sha1 as sha
+
+        fn = tmpdir.join("testhashfile")
+        data = b"hello"
+        fn.write(data, mode="wb")
+        assert fn.computehash("md5") == md5(data).hexdigest()
+        assert fn.computehash("sha1") == sha(data).hexdigest()
+        with pytest.raises(ValueError):
+            fn.computehash("asdasd")
+
+    def test_remove_removes_readonly_file(self, tmpdir):
+        readonly_file = tmpdir.join("readonly").ensure()
+        readonly_file.chmod(0)
+        readonly_file.remove()
+        assert not readonly_file.check(exists=1)
+
+    def test_remove_removes_readonly_dir(self, tmpdir):
+        readonly_dir = tmpdir.join("readonlydir").ensure(dir=1)
+        readonly_dir.chmod(int("500", 8))
+        readonly_dir.remove()
+        assert not readonly_dir.check(exists=1)
+
+    def test_remove_removes_dir_and_readonly_file(self, tmpdir):
+        readonly_dir = tmpdir.join("readonlydir").ensure(dir=1)
+        readonly_file = readonly_dir.join("readonlyfile").ensure()
+        readonly_file.chmod(0)
+        readonly_dir.remove()
+        assert not readonly_dir.check(exists=1)
+
+    def test_remove_routes_ignore_errors(self, tmpdir, monkeypatch):
+        lst = []
+        monkeypatch.setattr("shutil.rmtree", lambda *args, **kwargs: lst.append(kwargs))
+        tmpdir.remove()
+        assert not lst[0]["ignore_errors"]
+        for val in (True, False):
+            lst[:] = []
+            tmpdir.remove(ignore_errors=val)
+            assert lst[0]["ignore_errors"] == val
+
+    def test_initialize_curdir(self):
+        assert str(local()) == os.getcwd()
+
+    @skiponwin32
+    def test_chdir_gone(self, path1):
+        p = path1.ensure("dir_to_be_removed", dir=1)
+        p.chdir()
+        p.remove()
+        pytest.raises(error.ENOENT, local)
+        assert path1.chdir() is None
+        assert os.getcwd() == str(path1)
+
+        with pytest.raises(error.ENOENT):
+            with p.as_cwd():
+                raise NotImplementedError
+
+    @skiponwin32
+    def test_chdir_gone_in_as_cwd(self, path1):
+        p = path1.ensure("dir_to_be_removed", dir=1)
+        p.chdir()
+        p.remove()
+
+        with path1.as_cwd() as old:
+            assert old is None
+
+    def test_as_cwd(self, path1):
+        dir = path1.ensure("subdir", dir=1)
+        old = local()
+        with dir.as_cwd() as x:
+            assert x == old
+            assert local() == dir
+        assert os.getcwd() == str(old)
+
+    def test_as_cwd_exception(self, path1):
+        old = local()
+        dir = path1.ensure("subdir", dir=1)
+        with pytest.raises(ValueError):
+            with dir.as_cwd():
+                raise ValueError()
+        assert old == local()
+
+    def test_initialize_reldir(self, path1):
+        with path1.as_cwd():
+            p = local("samplefile")
+            assert p.check()
+
+    def test_tilde_expansion(self, monkeypatch, tmpdir):
+        monkeypatch.setenv("HOME", str(tmpdir))
+        p = local("~", expanduser=True)
+        assert p == os.path.expanduser("~")
+
+    @pytest.mark.skipif(
+        not sys.platform.startswith("win32"), reason="case-insensitive only on windows"
+    )
+    def test_eq_hash_are_case_insensitive_on_windows(self):
+        a = local("/some/path")
+        b = local("/some/PATH")
+        assert a == b
+        assert hash(a) == hash(b)
+        assert a in {b}
+        assert a in {b: "b"}
+
+    def test_eq_with_strings(self, path1):
+        path1 = path1.join("sampledir")
+        path2 = str(path1)
+        assert path1 == path2
+        assert path2 == path1
+        path3 = path1.join("samplefile")
+        assert path3 != path2
+        assert path2 != path3
+
+    def test_eq_with_none(self, path1):
+        assert path1 != None  # noqa: E711
+
+    def test_eq_non_ascii_unicode(self, path1):
+        path2 = path1.join("temp")
+        path3 = path1.join("ação")
+        path4 = path1.join("ディレクトリ")
+
+        assert path2 != path3
+        assert path2 != path4
+        assert path4 != path3
+
+    def test_gt_with_strings(self, path1):
+        path2 = path1.join("sampledir")
+        path3 = str(path1.join("ttt"))
+        assert path3 > path2
+        assert path2 < path3
+        assert path2 < "ttt"
+        assert "ttt" > path2
+        path4 = path1.join("aaa")
+        lst = [path2, path4, path3]
+        assert sorted(lst) == [path4, path2, path3]
+
+    def test_open_and_ensure(self, path1):
+        p = path1.join("sub1", "sub2", "file")
+        with p.open("w", ensure=1, encoding="utf-8") as f:
+            f.write("hello")
+        assert p.read_text(encoding="utf-8") == "hello"
+
+    def test_write_and_ensure(self, path1):
+        p = path1.join("sub1", "sub2", "file")
+        p.write_text("hello", ensure=1, encoding="utf-8")
+        assert p.read_text(encoding="utf-8") == "hello"
+
+    @pytest.mark.parametrize("bin", (False, True))
+    def test_dump(self, tmpdir, bin):
+        path = tmpdir.join(f"dumpfile{int(bin)}")
+        try:
+            d = {"answer": 42}
+            path.dump(d, bin=bin)
+            f = path.open("rb+")
+            import pickle
+
+            dnew = pickle.load(f)
+            assert d == dnew
+        finally:
+            f.close()
+
+    def test_setmtime(self):
+        import tempfile
+        import time
+
+        try:
+            fd, name = tempfile.mkstemp()
+            os.close(fd)
+        except AttributeError:
+            name = tempfile.mktemp()
+            open(name, "w").close()
+        try:
+            mtime = int(time.time()) - 100
+            path = local(name)
+            assert path.mtime() != mtime
+            path.setmtime(mtime)
+            assert path.mtime() == mtime
+            path.setmtime()
+            assert path.mtime() != mtime
+        finally:
+            os.remove(name)
+
+    def test_normpath(self, path1):
+        new1 = path1.join("/otherdir")
+        new2 = path1.join("otherdir")
+        assert str(new1) == str(new2)
+
+    def test_mkdtemp_creation(self):
+        d = local.mkdtemp()
+        try:
+            assert d.check(dir=1)
+        finally:
+            d.remove(rec=1)
+
+    def test_tmproot(self):
+        d = local.mkdtemp()
+        tmproot = local.get_temproot()
+        try:
+            assert d.check(dir=1)
+            assert d.dirpath() == tmproot
+        finally:
+            d.remove(rec=1)
+
+    def test_chdir(self, tmpdir):
+        old = local()
+        try:
+            res = tmpdir.chdir()
+            assert str(res) == str(old)
+            assert os.getcwd() == str(tmpdir)
+        finally:
+            old.chdir()
+
+    def test_ensure_filepath_withdir(self, tmpdir):
+        newfile = tmpdir.join("test1", "test")
+        newfile.ensure()
+        assert newfile.check(file=1)
+        newfile.write_text("42", encoding="utf-8")
+        newfile.ensure()
+        s = newfile.read_text(encoding="utf-8")
+        assert s == "42"
+
+    def test_ensure_filepath_withoutdir(self, tmpdir):
+        newfile = tmpdir.join("test1file")
+        t = newfile.ensure()
+        assert t == newfile
+        assert newfile.check(file=1)
+
+    def test_ensure_dirpath(self, tmpdir):
+        newfile = tmpdir.join("test1", "testfile")
+        t = newfile.ensure(dir=1)
+        assert t == newfile
+        assert newfile.check(dir=1)
+
+    def test_ensure_non_ascii_unicode(self, tmpdir):
+        newfile = tmpdir.join("ação", "ディレクトリ")
+        t = newfile.ensure(dir=1)
+        assert t == newfile
+        assert newfile.check(dir=1)
+
+    @pytest.mark.xfail(run=False, reason="unreliable est for long filenames")
+    def test_long_filenames(self, tmpdir):
+        if sys.platform == "win32":
+            pytest.skip("win32: work around needed for path length limit")
+        # see http://codespeak.net/pipermail/py-dev/2008q2/000922.html
+
+        # testing paths > 260 chars (which is Windows' limitation, but
+        # depending on how the paths are used), but > 4096 (which is the
+        # Linux' limitation) - the behaviour of paths with names > 4096 chars
+        # is undetermined
+        newfilename = "/test" * 60  # type:ignore[unreachable,unused-ignore]
+        l1 = tmpdir.join(newfilename)
+        l1.ensure(file=True)
+        l1.write_text("foo", encoding="utf-8")
+        l2 = tmpdir.join(newfilename)
+        assert l2.read_text(encoding="utf-8") == "foo"
+
+    def test_visit_depth_first(self, tmpdir):
+        tmpdir.ensure("a", "1")
+        tmpdir.ensure("b", "2")
+        p3 = tmpdir.ensure("breadth")
+        lst = list(tmpdir.visit(lambda x: x.check(file=1)))
+        assert len(lst) == 3
+        # check that breadth comes last
+        assert lst[2] == p3
+
+    def test_visit_rec_fnmatch(self, tmpdir):
+        p1 = tmpdir.ensure("a", "123")
+        tmpdir.ensure(".b", "345")
+        lst = list(tmpdir.visit("???", rec="[!.]*"))
+        assert len(lst) == 1
+        # check that breadth comes last
+        assert lst[0] == p1
+
+    def test_fnmatch_file_abspath(self, tmpdir):
+        b = tmpdir.join("a", "b")
+        assert b.fnmatch(os.sep.join("ab"))
+        pattern = os.sep.join([str(tmpdir), "*", "b"])
+        assert b.fnmatch(pattern)
+
+    def test_sysfind(self):
+        name = (sys.platform == "win32" and "cmd") or "test"
+        x = local.sysfind(name)
+        assert x.check(file=1)
+        assert local.sysfind("jaksdkasldqwe") is None
+        assert local.sysfind(name, paths=[]) is None
+        x2 = local.sysfind(name, paths=[x.dirpath()])
+        assert x2 == x
+
+    def test_fspath_protocol_other_class(self, fake_fspath_obj):
+        # py.path is always absolute
+        py_path = local(fake_fspath_obj)
+        str_path = fake_fspath_obj.__fspath__()
+        assert py_path.check(endswith=str_path)
+        assert py_path.join(fake_fspath_obj).strpath == os.path.join(
+            py_path.strpath, str_path
+        )
+
+    @pytest.mark.xfail(
+        reason="#11603", raises=(error.EEXIST, error.ENOENT), strict=False
+    )
+    def test_make_numbered_dir_multiprocess_safe(self, tmpdir):
+        # https://github.com/pytest-dev/py/issues/30
+        with multiprocessing.Pool() as pool:
+            results = [
+                pool.apply_async(batch_make_numbered_dirs, [tmpdir, 100])
+                for _ in range(20)
+            ]
+            for r in results:
+                assert r.get()
+
+
+class TestExecutionOnWindows:
+    pytestmark = win32only
+
+    def test_sysfind_bat_exe_before(self, tmpdir, monkeypatch):
+        monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep)
+        tmpdir.ensure("hello")
+        h = tmpdir.ensure("hello.bat")
+        x = local.sysfind("hello")
+        assert x == h
+
+
+class TestExecution:
+    pytestmark = skiponwin32
+
+    def test_sysfind_no_permission_ignored(self, monkeypatch, tmpdir):
+        noperm = tmpdir.ensure("noperm", dir=True)
+        monkeypatch.setenv("PATH", str(noperm), prepend=":")
+        noperm.chmod(0)
+        try:
+            assert local.sysfind("jaksdkasldqwe") is None
+        finally:
+            noperm.chmod(0o644)
+
+    def test_sysfind_absolute(self):
+        x = local.sysfind("test")
+        assert x.check(file=1)
+        y = local.sysfind(str(x))
+        assert y.check(file=1)
+        assert y == x
+
+    def test_sysfind_multiple(self, tmpdir, monkeypatch):
+        monkeypatch.setenv(
+            "PATH", "{}:{}".format(tmpdir.ensure("a"), tmpdir.join("b")), prepend=":"
+        )
+        tmpdir.ensure("b", "a")
+        x = local.sysfind("a", checker=lambda x: x.dirpath().basename == "b")
+        assert x.basename == "a"
+        assert x.dirpath().basename == "b"
+        assert local.sysfind("a", checker=lambda x: None) is None
+
+    def test_sysexec(self):
+        x = local.sysfind("ls")
+        out = x.sysexec("-a")
+        for x in local().listdir():
+            assert out.find(x.basename) != -1
+
+    def test_sysexec_failing(self):
+        try:
+            from py._process.cmdexec import ExecutionFailed  # py library
+        except ImportError:
+            ExecutionFailed = RuntimeError  # py vendored
+        x = local.sysfind("false")
+        with pytest.raises(ExecutionFailed):
+            x.sysexec("aksjdkasjd")
+
+    def test_make_numbered_dir(self, tmpdir):
+        tmpdir.ensure("base.not_an_int", dir=1)
+        for i in range(10):
+            numdir = local.make_numbered_dir(
+                prefix="base.", rootdir=tmpdir, keep=2, lock_timeout=0
+            )
+            assert numdir.check()
+            assert numdir.basename == f"base.{i}"
+            if i >= 1:
+                assert numdir.new(ext=str(i - 1)).check()
+            if i >= 2:
+                assert numdir.new(ext=str(i - 2)).check()
+            if i >= 3:
+                assert not numdir.new(ext=str(i - 3)).check()
+
+    def test_make_numbered_dir_case(self, tmpdir):
+        """make_numbered_dir does not make assumptions on the underlying
+        filesystem based on the platform and will assume it _could_ be case
+        insensitive.
+
+        See issues:
+        - https://github.com/pytest-dev/pytest/issues/708
+        - https://github.com/pytest-dev/pytest/issues/3451
+        """
+        d1 = local.make_numbered_dir(
+            prefix="CAse.",
+            rootdir=tmpdir,
+            keep=2,
+            lock_timeout=0,
+        )
+        d2 = local.make_numbered_dir(
+            prefix="caSE.",
+            rootdir=tmpdir,
+            keep=2,
+            lock_timeout=0,
+        )
+        assert str(d1).lower() != str(d2).lower()
+        assert str(d2).endswith(".1")
+
+    def test_make_numbered_dir_NotImplemented_Error(self, tmpdir, monkeypatch):
+        def notimpl(x, y):
+            raise NotImplementedError(42)
+
+        monkeypatch.setattr(os, "symlink", notimpl)
+        x = tmpdir.make_numbered_dir(rootdir=tmpdir, lock_timeout=0)
+        assert x.relto(tmpdir)
+        assert x.check()
+
+    def test_locked_make_numbered_dir(self, tmpdir):
+        for i in range(10):
+            numdir = local.make_numbered_dir(prefix="base2.", rootdir=tmpdir, keep=2)
+            assert numdir.check()
+            assert numdir.basename == f"base2.{i}"
+            for j in range(i):
+                assert numdir.new(ext=str(j)).check()
+
+    def test_error_preservation(self, path1):
+        pytest.raises(EnvironmentError, path1.join("qwoeqiwe").mtime)
+        pytest.raises(EnvironmentError, path1.join("qwoeqiwe").read)
+
+    # def test_parentdirmatch(self):
+    #    local.parentdirmatch('std', startmodule=__name__)
+    #
+
+
+class TestImport:
+    @pytest.fixture(autouse=True)
+    def preserve_sys(self):
+        with mock.patch.dict(sys.modules):
+            with mock.patch.object(sys, "path", list(sys.path)):
+                yield
+
+    def test_pyimport(self, path1):
+        obj = path1.join("execfile.py").pyimport()
+        assert obj.x == 42
+        assert obj.__name__ == "execfile"
+
+    def test_pyimport_renamed_dir_creates_mismatch(self, tmpdir, monkeypatch):
+        p = tmpdir.ensure("a", "test_x123.py")
+        p.pyimport()
+        tmpdir.join("a").move(tmpdir.join("b"))
+        with pytest.raises(tmpdir.ImportMismatchError):
+            tmpdir.join("b", "test_x123.py").pyimport()
+
+        # Errors can be ignored.
+        monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1")
+        tmpdir.join("b", "test_x123.py").pyimport()
+
+        # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error.
+        monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0")
+        with pytest.raises(tmpdir.ImportMismatchError):
+            tmpdir.join("b", "test_x123.py").pyimport()
+
+    def test_pyimport_messy_name(self, tmpdir):
+        # http://bitbucket.org/hpk42/py-trunk/issue/129
+        path = tmpdir.ensure("foo__init__.py")
+        path.pyimport()
+
+    def test_pyimport_dir(self, tmpdir):
+        p = tmpdir.join("hello_123")
+        p_init = p.ensure("__init__.py")
+        m = p.pyimport()
+        assert m.__name__ == "hello_123"
+        m = p_init.pyimport()
+        assert m.__name__ == "hello_123"
+
+    def test_pyimport_execfile_different_name(self, path1):
+        obj = path1.join("execfile.py").pyimport(modname="0x.y.z")
+        assert obj.x == 42
+        assert obj.__name__ == "0x.y.z"
+
+    def test_pyimport_a(self, path1):
+        otherdir = path1.join("otherdir")
+        mod = otherdir.join("a.py").pyimport()
+        assert mod.result == "got it"
+        assert mod.__name__ == "otherdir.a"
+
+    def test_pyimport_b(self, path1):
+        otherdir = path1.join("otherdir")
+        mod = otherdir.join("b.py").pyimport()
+        assert mod.stuff == "got it"
+        assert mod.__name__ == "otherdir.b"
+
+    def test_pyimport_c(self, path1):
+        otherdir = path1.join("otherdir")
+        mod = otherdir.join("c.py").pyimport()
+        assert mod.value == "got it"
+
+    def test_pyimport_d(self, path1):
+        otherdir = path1.join("otherdir")
+        mod = otherdir.join("d.py").pyimport()
+        assert mod.value2 == "got it"
+
+    def test_pyimport_and_import(self, tmpdir):
+        tmpdir.ensure("xxxpackage", "__init__.py")
+        mod1path = tmpdir.ensure("xxxpackage", "module1.py")
+        mod1 = mod1path.pyimport()
+        assert mod1.__name__ == "xxxpackage.module1"
+        from xxxpackage import module1
+
+        assert module1 is mod1
+
+    def test_pyimport_check_filepath_consistency(self, monkeypatch, tmpdir):
+        name = "pointsback123"
+        ModuleType = type(os)
+        p = tmpdir.ensure(name + ".py")
+        with monkeypatch.context() as mp:
+            for ending in (".pyc", "$py.class", ".pyo"):
+                mod = ModuleType(name)
+                pseudopath = tmpdir.ensure(name + ending)
+                mod.__file__ = str(pseudopath)
+                mp.setitem(sys.modules, name, mod)
+                newmod = p.pyimport()
+                assert mod == newmod
+        mod = ModuleType(name)
+        pseudopath = tmpdir.ensure(name + "123.py")
+        mod.__file__ = str(pseudopath)
+        monkeypatch.setitem(sys.modules, name, mod)
+        excinfo = pytest.raises(pseudopath.ImportMismatchError, p.pyimport)
+        modname, modfile, orig = excinfo.value.args
+        assert modname == name
+        assert modfile == pseudopath
+        assert orig == p
+        assert issubclass(pseudopath.ImportMismatchError, ImportError)
+
+    def test_issue131_pyimport_on__init__(self, tmpdir):
+        # __init__.py files may be namespace packages, and thus the
+        # __file__ of an imported module may not be ourselves
+        # see issue
+        p1 = tmpdir.ensure("proja", "__init__.py")
+        p2 = tmpdir.ensure("sub", "proja", "__init__.py")
+        m1 = p1.pyimport()
+        m2 = p2.pyimport()
+        assert m1 == m2
+
+    def test_ensuresyspath_append(self, tmpdir):
+        root1 = tmpdir.mkdir("root1")
+        file1 = root1.ensure("x123.py")
+        assert str(root1) not in sys.path
+        file1.pyimport(ensuresyspath="append")
+        assert str(root1) == sys.path[-1]
+        assert str(root1) not in sys.path[:-1]
+
+
+class TestImportlibImport:
+    OPTS = {"ensuresyspath": "importlib"}
+
+    def test_pyimport(self, path1):
+        obj = path1.join("execfile.py").pyimport(**self.OPTS)
+        assert obj.x == 42
+        assert obj.__name__ == "execfile"
+
+    def test_pyimport_dir_fails(self, tmpdir):
+        p = tmpdir.join("hello_123")
+        p.ensure("__init__.py")
+        with pytest.raises(ImportError):
+            p.pyimport(**self.OPTS)
+
+    def test_pyimport_execfile_different_name(self, path1):
+        obj = path1.join("execfile.py").pyimport(modname="0x.y.z", **self.OPTS)
+        assert obj.x == 42
+        assert obj.__name__ == "0x.y.z"
+
+    def test_pyimport_relative_import_fails(self, path1):
+        otherdir = path1.join("otherdir")
+        with pytest.raises(ImportError):
+            otherdir.join("a.py").pyimport(**self.OPTS)
+
+    def test_pyimport_doesnt_use_sys_modules(self, tmpdir):
+        p = tmpdir.ensure("file738jsk.py")
+        mod = p.pyimport(**self.OPTS)
+        assert mod.__name__ == "file738jsk"
+        assert "file738jsk" not in sys.modules
+
+
+def test_pypkgdir(tmpdir):
+    pkg = tmpdir.ensure("pkg1", dir=1)
+    pkg.ensure("__init__.py")
+    pkg.ensure("subdir/__init__.py")
+    assert pkg.pypkgpath() == pkg
+    assert pkg.join("subdir", "__init__.py").pypkgpath() == pkg
+
+
+def test_pypkgdir_unimportable(tmpdir):
+    pkg = tmpdir.ensure("pkg1-1", dir=1)  # unimportable
+    pkg.ensure("__init__.py")
+    subdir = pkg.ensure("subdir/__init__.py").dirpath()
+    assert subdir.pypkgpath() == subdir
+    assert subdir.ensure("xyz.py").pypkgpath() == subdir
+    assert not pkg.pypkgpath()
+
+
+def test_isimportable():
+    try:
+        from py.path import isimportable  # py vendored version
+    except ImportError:
+        from py._path.local import isimportable  # py library
+
+    assert not isimportable("")
+    assert isimportable("x")
+    assert isimportable("x1")
+    assert isimportable("x_1")
+    assert isimportable("_")
+    assert isimportable("_1")
+    assert not isimportable("x-1")
+    assert not isimportable("x:1")
+
+
+def test_homedir_from_HOME(monkeypatch):
+    path = os.getcwd()
+    monkeypatch.setenv("HOME", path)
+    assert local._gethomedir() == local(path)
+
+
+def test_homedir_not_exists(monkeypatch):
+    monkeypatch.delenv("HOME", raising=False)
+    monkeypatch.delenv("HOMEDRIVE", raising=False)
+    homedir = local._gethomedir()
+    assert homedir is None
+
+
+def test_samefile(tmpdir):
+    assert tmpdir.samefile(tmpdir)
+    p = tmpdir.ensure("hello")
+    assert p.samefile(p)
+    with p.dirpath().as_cwd():
+        assert p.samefile(p.basename)
+    if sys.platform == "win32":
+        p1 = p.__class__(str(p).lower())
+        p2 = p.__class__(str(p).upper())
+        assert p1.samefile(p2)
+
+
+@pytest.mark.skipif(not hasattr(os, "symlink"), reason="os.symlink not available")
+def test_samefile_symlink(tmpdir):
+    p1 = tmpdir.ensure("foo.txt")
+    p2 = tmpdir.join("linked.txt")
+    try:
+        os.symlink(str(p1), str(p2))
+    except (OSError, NotImplementedError) as e:
+        # on Windows this might fail if the user doesn't have special symlink permissions
+        # pypy3 on Windows doesn't implement os.symlink and raises NotImplementedError
+        pytest.skip(str(e.args[0]))
+
+    assert p1.samefile(p2)
+
+
+def test_listdir_single_arg(tmpdir):
+    tmpdir.ensure("hello")
+    assert tmpdir.listdir("hello")[0].basename == "hello"
+
+
+def test_mkdtemp_rootdir(tmpdir):
+    dtmp = local.mkdtemp(rootdir=tmpdir)
+    assert tmpdir.listdir() == [dtmp]
+
+
+class TestWINLocalPath:
+    pytestmark = win32only
+
+    def test_owner_group_not_implemented(self, path1):
+        with pytest.raises(NotImplementedError):
+            _ = path1.stat().owner
+        with pytest.raises(NotImplementedError):
+            _ = path1.stat().group
+
+    def test_chmod_simple_int(self, path1):
+        mode = path1.stat().mode
+        # Ensure that we actually change the mode to something different.
+        path1.chmod((mode == 0 and 1) or 0)
+        try:
+            print(path1.stat().mode)
+            print(mode)
+            assert path1.stat().mode != mode
+        finally:
+            path1.chmod(mode)
+            assert path1.stat().mode == mode
+
+    def test_path_comparison_lowercase_mixed(self, path1):
+        t1 = path1.join("a_path")
+        t2 = path1.join("A_path")
+        assert t1 == t1
+        assert t1 == t2
+
+    def test_relto_with_mixed_case(self, path1):
+        t1 = path1.join("a_path", "fiLe")
+        t2 = path1.join("A_path")
+        assert t1.relto(t2) == "fiLe"
+
+    def test_allow_unix_style_paths(self, path1):
+        t1 = path1.join("a_path")
+        assert t1 == str(path1) + "\\a_path"
+        t1 = path1.join("a_path/")
+        assert t1 == str(path1) + "\\a_path"
+        t1 = path1.join("dir/a_path")
+        assert t1 == str(path1) + "\\dir\\a_path"
+
+    def test_sysfind_in_currentdir(self, path1):
+        cmd = local.sysfind("cmd")
+        root = cmd.new(dirname="", basename="")  # c:\ in most installations
+        with root.as_cwd():
+            x = local.sysfind(cmd.relto(root))
+            assert x.check(file=1)
+
+    def test_fnmatch_file_abspath_posix_pattern_on_win32(self, tmpdir):
+        # path-matching patterns might contain a posix path separator '/'
+        # Test that we can match that pattern on windows.
+        import posixpath
+
+        b = tmpdir.join("a", "b")
+        assert b.fnmatch(posixpath.sep.join("ab"))
+        pattern = posixpath.sep.join([str(tmpdir), "*", "b"])
+        assert b.fnmatch(pattern)
+
+
+class TestPOSIXLocalPath:
+    pytestmark = skiponwin32
+
+    def test_hardlink(self, tmpdir):
+        linkpath = tmpdir.join("test")
+        filepath = tmpdir.join("file")
+        filepath.write_text("Hello", encoding="utf-8")
+        nlink = filepath.stat().nlink
+        linkpath.mklinkto(filepath)
+        assert filepath.stat().nlink == nlink + 1
+
+    def test_symlink_are_identical(self, tmpdir):
+        filepath = tmpdir.join("file")
+        filepath.write_text("Hello", encoding="utf-8")
+        linkpath = tmpdir.join("test")
+        linkpath.mksymlinkto(filepath)
+        assert linkpath.readlink() == str(filepath)
+
+    def test_symlink_isfile(self, tmpdir):
+        linkpath = tmpdir.join("test")
+        filepath = tmpdir.join("file")
+        filepath.write_text("", encoding="utf-8")
+        linkpath.mksymlinkto(filepath)
+        assert linkpath.check(file=1)
+        assert not linkpath.check(link=0, file=1)
+        assert linkpath.islink()
+
+    def test_symlink_relative(self, tmpdir):
+        linkpath = tmpdir.join("test")
+        filepath = tmpdir.join("file")
+        filepath.write_text("Hello", encoding="utf-8")
+        linkpath.mksymlinkto(filepath, absolute=False)
+        assert linkpath.readlink() == "file"
+        assert filepath.read_text(encoding="utf-8") == linkpath.read_text(
+            encoding="utf-8"
+        )
+
+    def test_symlink_not_existing(self, tmpdir):
+        linkpath = tmpdir.join("testnotexisting")
+        assert not linkpath.check(link=1)
+        assert linkpath.check(link=0)
+
+    def test_relto_with_root(self, path1, tmpdir):
+        y = path1.join("x").relto(local("/"))
+        assert y[0] == str(path1)[1]
+
+    def test_visit_recursive_symlink(self, tmpdir):
+        linkpath = tmpdir.join("test")
+        linkpath.mksymlinkto(tmpdir)
+        visitor = tmpdir.visit(None, lambda x: x.check(link=0))
+        assert list(visitor) == [linkpath]
+
+    def test_symlink_isdir(self, tmpdir):
+        linkpath = tmpdir.join("test")
+        linkpath.mksymlinkto(tmpdir)
+        assert linkpath.check(dir=1)
+        assert not linkpath.check(link=0, dir=1)
+
+    def test_symlink_remove(self, tmpdir):
+        linkpath = tmpdir.join("test")
+        linkpath.mksymlinkto(linkpath)  # point to itself
+        assert linkpath.check(link=1)
+        linkpath.remove()
+        assert not linkpath.check()
+
+    def test_realpath_file(self, tmpdir):
+        linkpath = tmpdir.join("test")
+        filepath = tmpdir.join("file")
+        filepath.write_text("", encoding="utf-8")
+        linkpath.mksymlinkto(filepath)
+        realpath = linkpath.realpath()
+        assert realpath.basename == "file"
+
+    def test_owner(self, path1, tmpdir):
+        from grp import getgrgid  # type:ignore[attr-defined,unused-ignore]
+        from pwd import getpwuid  # type:ignore[attr-defined,unused-ignore]
+
+        stat = path1.stat()
+        assert stat.path == path1
+
+        uid = stat.uid
+        gid = stat.gid
+        owner = getpwuid(uid)[0]
+        group = getgrgid(gid)[0]
+
+        assert uid == stat.uid
+        assert owner == stat.owner
+        assert gid == stat.gid
+        assert group == stat.group
+
+    def test_stat_helpers(self, tmpdir, monkeypatch):
+        path1 = tmpdir.ensure("file")
+        stat1 = path1.stat()
+        stat2 = tmpdir.stat()
+        assert stat1.isfile()
+        assert stat2.isdir()
+        assert not stat1.islink()
+        assert not stat2.islink()
+
+    def test_stat_non_raising(self, tmpdir):
+        path1 = tmpdir.join("file")
+        pytest.raises(error.ENOENT, lambda: path1.stat())
+        res = path1.stat(raising=False)
+        assert res is None
+
+    def test_atime(self, tmpdir):
+        import time
+
+        path = tmpdir.ensure("samplefile")
+        now = time.time()
+        atime1 = path.atime()
+        # we could wait here but timer resolution is very
+        # system dependent
+        path.read_binary()
+        time.sleep(ATIME_RESOLUTION)
+        atime2 = path.atime()
+        time.sleep(ATIME_RESOLUTION)
+        duration = time.time() - now
+        assert (atime2 - atime1) <= duration
+
+    def test_commondir(self, path1):
+        # XXX This is here in local until we find a way to implement this
+        #     using the subversion command line api.
+        p1 = path1.join("something")
+        p2 = path1.join("otherthing")
+        assert p1.common(p2) == path1
+        assert p2.common(p1) == path1
+
+    def test_commondir_nocommon(self, path1):
+        # XXX This is here in local until we find a way to implement this
+        #     using the subversion command line api.
+        p1 = path1.join("something")
+        p2 = local(path1.sep + "blabla")
+        assert p1.common(p2) == "/"
+
+    def test_join_to_root(self, path1):
+        root = path1.parts()[0]
+        assert len(str(root)) == 1
+        assert str(root.join("a")) == "/a"
+
+    def test_join_root_to_root_with_no_abs(self, path1):
+        nroot = path1.join("/")
+        assert str(path1) == str(nroot)
+        assert path1 == nroot
+
+    def test_chmod_simple_int(self, path1):
+        mode = path1.stat().mode
+        path1.chmod(int(mode / 2))
+        try:
+            assert path1.stat().mode != mode
+        finally:
+            path1.chmod(mode)
+            assert path1.stat().mode == mode
+
+    def test_chmod_rec_int(self, path1):
+        # XXX fragile test
+        def recfilter(x):
+            return x.check(dotfile=0, link=0)
+
+        oldmodes = {}
+        for x in path1.visit(rec=recfilter):
+            oldmodes[x] = x.stat().mode
+        path1.chmod(int("772", 8), rec=recfilter)
+        try:
+            for x in path1.visit(rec=recfilter):
+                assert x.stat().mode & int("777", 8) == int("772", 8)
+        finally:
+            for x, y in oldmodes.items():
+                x.chmod(y)
+
+    def test_copy_archiving(self, tmpdir):
+        unicode_fn = "something-\342\200\223.txt"
+        f = tmpdir.ensure("a", unicode_fn)
+        a = f.dirpath()
+        oldmode = f.stat().mode
+        newmode = oldmode ^ 1
+        f.chmod(newmode)
+        b = tmpdir.join("b")
+        a.copy(b, mode=True)
+        assert b.join(f.basename).stat().mode == newmode
+
+    def test_copy_stat_file(self, tmpdir):
+        src = tmpdir.ensure("src")
+        dst = tmpdir.join("dst")
+        # a small delay before the copy
+        time.sleep(ATIME_RESOLUTION)
+        src.copy(dst, stat=True)
+        oldstat = src.stat()
+        newstat = dst.stat()
+        assert oldstat.mode == newstat.mode
+        assert (dst.atime() - src.atime()) < ATIME_RESOLUTION
+        assert (dst.mtime() - src.mtime()) < ATIME_RESOLUTION
+
+    def test_copy_stat_dir(self, tmpdir):
+        test_files = ["a", "b", "c"]
+        src = tmpdir.join("src")
+        for f in test_files:
+            src.join(f).write_text(f, ensure=True, encoding="utf-8")
+        dst = tmpdir.join("dst")
+        # a small delay before the copy
+        time.sleep(ATIME_RESOLUTION)
+        src.copy(dst, stat=True)
+        for f in test_files:
+            oldstat = src.join(f).stat()
+            newstat = dst.join(f).stat()
+            assert (newstat.atime - oldstat.atime) < ATIME_RESOLUTION
+            assert (newstat.mtime - oldstat.mtime) < ATIME_RESOLUTION
+            assert oldstat.mode == newstat.mode
+
+    def test_chown_identity(self, path1):
+        owner = path1.stat().owner
+        group = path1.stat().group
+        path1.chown(owner, group)
+
+    def test_chown_dangling_link(self, path1):
+        owner = path1.stat().owner
+        group = path1.stat().group
+        x = path1.join("hello")
+        x.mksymlinkto("qlwkejqwlek")
+        try:
+            path1.chown(owner, group, rec=1)
+        finally:
+            x.remove(rec=0)
+
+    def test_chown_identity_rec_mayfail(self, path1):
+        owner = path1.stat().owner
+        group = path1.stat().group
+        path1.chown(owner, group)
+
+
+class TestUnicode:
+    def test_join_ensure(self, tmpdir, monkeypatch):
+        if "LANG" not in os.environ:
+            pytest.skip("cannot run test without locale")
+        x = local(tmpdir.strpath)
+        part = "hällo"
+        y = x.ensure(part)
+        assert x.join(part) == y
+
+    def test_listdir(self, tmpdir):
+        if "LANG" not in os.environ:
+            pytest.skip("cannot run test without locale")
+        x = local(tmpdir.strpath)
+        part = "hällo"
+        y = x.ensure(part)
+        assert x.listdir(part)[0] == y
+
+    @pytest.mark.xfail(reason="changing read/write might break existing usages")
+    def test_read_write(self, tmpdir):
+        x = tmpdir.join("hello")
+        part = "hällo"
+        with ignore_encoding_warning():
+            x.write(part)
+            assert x.read() == part
+            x.write(part.encode(sys.getdefaultencoding()))
+            assert x.read() == part.encode(sys.getdefaultencoding())
+
+
+class TestBinaryAndTextMethods:
+    def test_read_binwrite(self, tmpdir):
+        x = tmpdir.join("hello")
+        part = "hällo"
+        part_utf8 = part.encode("utf8")
+        x.write_binary(part_utf8)
+        assert x.read_binary() == part_utf8
+        s = x.read_text(encoding="utf8")
+        assert s == part
+        assert isinstance(s, str)
+
+    def test_read_textwrite(self, tmpdir):
+        x = tmpdir.join("hello")
+        part = "hällo"
+        part_utf8 = part.encode("utf8")
+        x.write_text(part, encoding="utf8")
+        assert x.read_binary() == part_utf8
+        assert x.read_text(encoding="utf8") == part
+
+    def test_default_encoding(self, tmpdir):
+        x = tmpdir.join("hello")
+        # Can't use UTF8 as the default encoding (ASCII) doesn't support it
+        part = "hello"
+        x.write_text(part, "ascii")
+        s = x.read_text("ascii")
+        assert s == part
+        assert type(s) is type(part)
diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py
index bfd1fe6e668..50ea4ff44d2 100644
--- a/testing/acceptance_test.py
+++ b/testing/acceptance_test.py
@@ -1,14 +1,19 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Sequence
+import dataclasses
+import importlib.metadata
 import os
+from pathlib import Path
+import subprocess
 import sys
 import types
 
-import attr
-
-import pytest
-from _pytest.compat import importlib_metadata
 from _pytest.config import ExitCode
 from _pytest.pathlib import symlink_or_skip
 from _pytest.pytester import Pytester
+import pytest
 
 
 def prepend_pythonpath(*dirs) -> str:
@@ -115,11 +120,11 @@ def test_early_load_setuptools_name(
 
         loaded = []
 
-        @attr.s
+        @dataclasses.dataclass
         class DummyEntryPoint:
-            name = attr.ib()
-            module = attr.ib()
-            group = "pytest11"
+            name: str
+            module: str
+            group: str = "pytest11"
 
             def load(self):
                 __import__(self.module)
@@ -132,15 +137,15 @@ def load(self):
             DummyEntryPoint("mycov", "mycov_module"),
         ]
 
-        @attr.s
+        @dataclasses.dataclass
         class DummyDist:
-            entry_points = attr.ib()
-            files = ()
+            entry_points: object
+            files: object = ()
 
         def my_dists():
             return (DummyDist(entry_points),)
 
-        monkeypatch.setattr(importlib_metadata, "distributions", my_dists)
+        monkeypatch.setattr(importlib.metadata, "distributions", my_dists)
         params = ("-p", "mycov") if load_cov_early else ()
         pytester.runpytest_inprocess(*params)
         if load_cov_early:
@@ -187,7 +192,7 @@ def test_not_collectable_arguments(self, pytester: Pytester) -> None:
         result.stderr.fnmatch_lines(
             [
                 f"ERROR: not found: {p2}",
-                f"(no name {str(p2)!r} in any of [[][]])",
+                "(no match in any of *)",
                 "",
             ]
         )
@@ -269,7 +274,7 @@ def test_conftest_printing_shows_if_error(self, pytester: Pytester) -> None:
     def test_issue109_sibling_conftests_not_loaded(self, pytester: Pytester) -> None:
         sub1 = pytester.mkdir("sub1")
         sub2 = pytester.mkdir("sub2")
-        sub1.joinpath("conftest.py").write_text("assert 0")
+        sub1.joinpath("conftest.py").write_text("assert 0", encoding="utf-8")
         result = pytester.runpytest(sub2)
         assert result.ret == ExitCode.NO_TESTS_COLLECTED
         sub2.joinpath("__init__.py").touch()
@@ -343,6 +348,45 @@ def test_func(i):
         assert res.ret == 0
         res.stdout.fnmatch_lines(["*1 passed*"])
 
+    def test_direct_addressing_selects_duplicates(self, pytester: Pytester) -> None:
+        p = pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize("a", [1, 2, 10, 11, 2, 1, 12, 11])
+            def test_func(a):
+                pass
+            """
+        )
+        result = pytester.runpytest(p)
+        result.assert_outcomes(failed=0, passed=8)
+
+    def test_direct_addressing_selects_duplicates_1(self, pytester: Pytester) -> None:
+        p = pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize("a", [1, 2, 10, 11, 2, 1, 12, 1_1,2_1])
+            def test_func(a):
+                pass
+            """
+        )
+        result = pytester.runpytest(p)
+        result.assert_outcomes(failed=0, passed=9)
+
+    def test_direct_addressing_selects_duplicates_2(self, pytester: Pytester) -> None:
+        p = pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize("a", ["a","b","c","a","a1"])
+            def test_func(a):
+                pass
+            """
+        )
+        result = pytester.runpytest(p)
+        result.assert_outcomes(failed=0, passed=5)
+
     def test_direct_addressing_notfound(self, pytester: Pytester) -> None:
         p = pytester.makepyfile(
             """
@@ -359,7 +403,7 @@ def test_docstring_on_hookspec(self) -> None:
 
         for name, value in vars(hookspec).items():
             if name.startswith("pytest_"):
-                assert value.__doc__, "no docstring for %s" % name
+                assert value.__doc__, f"no docstring for {name}"
 
     def test_initialization_error_issue49(self, pytester: Pytester) -> None:
         pytester.makeconftest(
@@ -469,7 +513,7 @@ def test_plugins_given_as_strings(
         assert "invalid" in str(excinfo.value)
 
         p = pytester.path.joinpath("test_test_plugins_given_as_strings.py")
-        p.write_text("def test_foo(): pass")
+        p.write_text("def test_foo(): pass", encoding="utf-8")
         mod = types.ModuleType("myplugin")
         monkeypatch.setitem(sys.modules, "myplugin", mod)
         assert pytest.main(args=[str(pytester.path)], plugins=["myplugin"]) == 0
@@ -501,6 +545,32 @@ def test_foo(data):
         res = pytester.runpytest(p)
         res.assert_outcomes(passed=3)
 
+    # Warning ignore because of:
+    # https://github.com/python/cpython/issues/85308
+    # Can be removed once Python<3.12 support is dropped.
+    @pytest.mark.filterwarnings("ignore:'encoding' argument not specified")
+    def test_command_line_args_from_file(
+        self, pytester: Pytester, tmp_path: Path
+    ) -> None:
+        pytester.makepyfile(
+            test_file="""
+            import pytest
+
+            class TestClass:
+                @pytest.mark.parametrize("a", ["x","y"])
+                def test_func(self, a):
+                    pass
+            """
+        )
+        tests = [
+            "test_file.py::TestClass::test_func[x]",
+            "test_file.py::TestClass::test_func[y]",
+            "-q",
+        ]
+        args_file = pytester.maketxtfile(tests="\n".join(tests))
+        result = pytester.runpytest(f"@{args_file}")
+        result.assert_outcomes(failed=0, passed=2)
+
 
 class TestInvocationVariants:
     def test_earlyinit(self, pytester: Pytester) -> None:
@@ -589,7 +659,7 @@ def pytest_addoption(self, parser):
     def test_pyargs_importerror(self, pytester: Pytester, monkeypatch) -> None:
         monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False)
         path = pytester.mkpydir("tpkg")
-        path.joinpath("test_hello.py").write_text("raise ImportError")
+        path.joinpath("test_hello.py").write_text("raise ImportError", encoding="utf-8")
 
         result = pytester.runpytest("--pyargs", "tpkg.test_hello", syspathinsert=True)
         assert result.ret != 0
@@ -599,10 +669,10 @@ def test_pyargs_importerror(self, pytester: Pytester, monkeypatch) -> None:
     def test_pyargs_only_imported_once(self, pytester: Pytester) -> None:
         pkg = pytester.mkpydir("foo")
         pkg.joinpath("test_foo.py").write_text(
-            "print('hello from test_foo')\ndef test(): pass"
+            "print('hello from test_foo')\ndef test(): pass", encoding="utf-8"
         )
         pkg.joinpath("conftest.py").write_text(
-            "def pytest_configure(config): print('configuring')"
+            "def pytest_configure(config): print('configuring')", encoding="utf-8"
         )
 
         result = pytester.runpytest(
@@ -615,7 +685,7 @@ def test_pyargs_only_imported_once(self, pytester: Pytester) -> None:
 
     def test_pyargs_filename_looks_like_module(self, pytester: Pytester) -> None:
         pytester.path.joinpath("conftest.py").touch()
-        pytester.path.joinpath("t.py").write_text("def test(): pass")
+        pytester.path.joinpath("t.py").write_text("def test(): pass", encoding="utf-8")
         result = pytester.runpytest("--pyargs", "t.py")
         assert result.ret == ExitCode.OK
 
@@ -624,8 +694,12 @@ def test_cmdline_python_package(self, pytester: Pytester, monkeypatch) -> None:
 
         monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False)
         path = pytester.mkpydir("tpkg")
-        path.joinpath("test_hello.py").write_text("def test_hello(): pass")
-        path.joinpath("test_world.py").write_text("def test_world(): pass")
+        path.joinpath("test_hello.py").write_text(
+            "def test_hello(): pass", encoding="utf-8"
+        )
+        path.joinpath("test_world.py").write_text(
+            "def test_world(): pass", encoding="utf-8"
+        )
         result = pytester.runpytest("--pyargs", "tpkg")
         assert result.ret == 0
         result.stdout.fnmatch_lines(["*2 passed*"])
@@ -664,13 +738,15 @@ def test_cmdline_python_namespace_package(
             ns = d.joinpath("ns_pkg")
             ns.mkdir()
             ns.joinpath("__init__.py").write_text(
-                "__import__('pkg_resources').declare_namespace(__name__)"
+                "__import__('pkg_resources').declare_namespace(__name__)",
+                encoding="utf-8",
             )
             lib = ns.joinpath(dirname)
             lib.mkdir()
             lib.joinpath("__init__.py").touch()
             lib.joinpath(f"test_{dirname}.py").write_text(
-                f"def test_{dirname}(): pass\ndef test_other():pass"
+                f"def test_{dirname}(): pass\ndef test_other():pass",
+                encoding="utf-8",
             )
 
         # The structure of the test directory is now:
@@ -695,7 +771,18 @@ def test_cmdline_python_namespace_package(
 
         # mixed module and filenames:
         monkeypatch.chdir("world")
-        result = pytester.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
+
+        # pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages.
+        # pgk_resources has been deprecated entirely.
+        # While we could change the test to use implicit namespace packages, seems better
+        # to still ensure the old declaration via declare_namespace still works.
+        ignore_w = (
+            r"-Wignore:Deprecated call to `pkg_resources.declare_namespace",
+            r"-Wignore:pkg_resources is deprecated",
+        )
+        result = pytester.runpytest(
+            "--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", *ignore_w
+        )
         assert result.ret == 0
         result.stdout.fnmatch_lines(
             [
@@ -745,10 +832,10 @@ def test_cmdline_python_package_symlink(
         lib.mkdir()
         lib.joinpath("__init__.py").touch()
         lib.joinpath("test_bar.py").write_text(
-            "def test_bar(): pass\ndef test_other(a_fixture):pass"
+            "def test_bar(): pass\ndef test_other(a_fixture):pass", encoding="utf-8"
         )
         lib.joinpath("conftest.py").write_text(
-            "import pytest\n@pytest.fixture\ndef a_fixture():pass"
+            "import pytest\n@pytest.fixture\ndef a_fixture():pass", encoding="utf-8"
         )
 
         d_local = pytester.mkdir("symlink_root")
@@ -873,7 +960,6 @@ def test_calls(self, pytester: Pytester, mock_timing) -> None:
         )
 
     def test_calls_show_2(self, pytester: Pytester, mock_timing) -> None:
-
         pytester.makepyfile(self.source)
         result = pytester.runpytest_inprocess("--durations=2")
         assert result.ret == 0
@@ -885,28 +971,43 @@ def test_calls_showall(self, pytester: Pytester, mock_timing) -> None:
         pytester.makepyfile(self.source)
         result = pytester.runpytest_inprocess("--durations=0")
         assert result.ret == 0
-
-        tested = "3"
-        for x in tested:
-            for y in ("call",):  # 'setup', 'call', 'teardown':
-                for line in result.stdout.lines:
-                    if ("test_%s" % x) in line and y in line:
-                        break
-                else:
-                    raise AssertionError(f"not found {x} {y}")
+        TestDurations.check_tests_in_output(result.stdout.lines, 2, 3)
 
     def test_calls_showall_verbose(self, pytester: Pytester, mock_timing) -> None:
         pytester.makepyfile(self.source)
         result = pytester.runpytest_inprocess("--durations=0", "-vv")
         assert result.ret == 0
+        TestDurations.check_tests_in_output(result.stdout.lines, 1, 2, 3)
 
-        for x in "123":
-            for y in ("call",):  # 'setup', 'call', 'teardown':
-                for line in result.stdout.lines:
-                    if ("test_%s" % x) in line and y in line:
-                        break
-                else:
-                    raise AssertionError(f"not found {x} {y}")
+    def test_calls_showall_durationsmin(self, pytester: Pytester, mock_timing) -> None:
+        pytester.makepyfile(self.source)
+        result = pytester.runpytest_inprocess("--durations=0", "--durations-min=0.015")
+        assert result.ret == 0
+        TestDurations.check_tests_in_output(result.stdout.lines, 3)
+
+    def test_calls_showall_durationsmin_verbose(
+        self, pytester: Pytester, mock_timing
+    ) -> None:
+        pytester.makepyfile(self.source)
+        result = pytester.runpytest_inprocess(
+            "--durations=0", "--durations-min=0.015", "-vv"
+        )
+        assert result.ret == 0
+        TestDurations.check_tests_in_output(result.stdout.lines, 3)
+
+    @staticmethod
+    def check_tests_in_output(
+        lines: Sequence[str], *expected_test_numbers: int, number_of_tests: int = 3
+    ) -> None:
+        found_test_numbers = {
+            test_number
+            for test_number in range(1, number_of_tests + 1)
+            if any(
+                line.endswith(f"test_{test_number}") and " call " in line
+                for line in lines
+            )
+        }
+        assert found_test_numbers == set(expected_test_numbers)
 
     def test_with_deselected(self, pytester: Pytester, mock_timing) -> None:
         pytester.makepyfile(self.source)
@@ -1038,14 +1139,14 @@ def test_fixture_values_leak(pytester: Pytester) -> None:
     """
     pytester.makepyfile(
         """
-        import attr
+        import dataclasses
         import gc
         import pytest
         import weakref
 
-        @attr.s
-        class SomeObj(object):
-            name = attr.ib()
+        @dataclasses.dataclass
+        class SomeObj:
+            name: str
 
         fix_of_test1_ref = None
         session_ref = None
@@ -1150,8 +1251,7 @@ def test_usage_error_code(pytester: Pytester) -> None:
     assert result.ret == ExitCode.USAGE_ERROR
 
 
-@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning")
-def test_warn_on_async_function(pytester: Pytester) -> None:
+def test_error_on_async_function(pytester: Pytester) -> None:
     # In the below we .close() the coroutine only to avoid
     # "RuntimeWarning: coroutine 'test_2' was never awaited"
     # which messes with other tests.
@@ -1170,21 +1270,16 @@ def test_3():
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
         [
-            "test_async.py::test_1",
-            "test_async.py::test_2",
-            "test_async.py::test_3",
             "*async def functions are not natively supported*",
-            "*3 skipped, 3 warnings in*",
+            "*test_async.py::test_1*",
+            "*test_async.py::test_2*",
+            "*test_async.py::test_3*",
         ]
     )
-    # ensure our warning message appears only once
-    assert (
-        result.stdout.str().count("async def functions are not natively supported") == 1
-    )
+    result.assert_outcomes(failed=3)
 
 
-@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning")
-def test_warn_on_async_gen_function(pytester: Pytester) -> None:
+def test_error_on_async_gen_function(pytester: Pytester) -> None:
     pytester.makepyfile(
         test_async="""
         async def test_1():
@@ -1198,17 +1293,111 @@ def test_3():
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
         [
-            "test_async.py::test_1",
-            "test_async.py::test_2",
-            "test_async.py::test_3",
             "*async def functions are not natively supported*",
-            "*3 skipped, 3 warnings in*",
+            "*test_async.py::test_1*",
+            "*test_async.py::test_2*",
+            "*test_async.py::test_3*",
         ]
     )
-    # ensure our warning message appears only once
-    assert (
-        result.stdout.str().count("async def functions are not natively supported") == 1
+    result.assert_outcomes(failed=3)
+
+
+def test_warning_on_sync_test_async_fixture(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_sync="""
+            import pytest
+
+            @pytest.fixture
+            async def async_fixture():
+                ...
+
+            def test_foo(async_fixture):
+                # suppress unawaited coroutine warning
+                try:
+                    async_fixture.send(None)
+                except StopIteration:
+                    pass
+        """
     )
+    result = pytester.runpytest()
+    result.stdout.fnmatch_lines(
+        [
+            "*== warnings summary ==*",
+            (
+                "*PytestRemovedIn9Warning: 'test_foo' requested an async "
+                "fixture 'async_fixture', with no plugin or hook that handled it. "
+                "This is usually an error, as pytest does not natively support it. "
+                "This will turn into an error in pytest 9."
+            ),
+            "  See: https://docs.pytest.org/en/stable/deprecations.html#sync-test-depending-on-async-fixture",
+        ]
+    )
+    result.assert_outcomes(passed=1, warnings=1)
+
+
+def test_warning_on_sync_test_async_fixture_gen(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_sync="""
+            import pytest
+
+            @pytest.fixture
+            async def async_fixture():
+                yield
+
+            def test_foo(async_fixture):
+                # async gens don't emit unawaited-coroutine
+                ...
+        """
+    )
+    result = pytester.runpytest()
+    result.stdout.fnmatch_lines(
+        [
+            "*== warnings summary ==*",
+            (
+                "*PytestRemovedIn9Warning: 'test_foo' requested an async "
+                "fixture 'async_fixture', with no plugin or hook that handled it. "
+                "This is usually an error, as pytest does not natively support it. "
+                "This will turn into an error in pytest 9."
+            ),
+            "  See: https://docs.pytest.org/en/stable/deprecations.html#sync-test-depending-on-async-fixture",
+        ]
+    )
+    result.assert_outcomes(passed=1, warnings=1)
+
+
+def test_warning_on_sync_test_async_autouse_fixture(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_sync="""
+            import pytest
+
+            @pytest.fixture(autouse=True)
+            async def async_fixture():
+                ...
+
+            # We explicitly request the fixture to be able to
+            # suppress the RuntimeWarning for unawaited coroutine.
+            def test_foo(async_fixture):
+                try:
+                    async_fixture.send(None)
+                except StopIteration:
+                    pass
+        """
+    )
+    result = pytester.runpytest()
+    result.stdout.fnmatch_lines(
+        [
+            "*== warnings summary ==*",
+            (
+                "*PytestRemovedIn9Warning: 'test_foo' requested an async "
+                "fixture 'async_fixture' with autouse=True, with no plugin or hook "
+                "that handled it. "
+                "This is usually an error, as pytest does not natively support it. "
+                "This will turn into an error in pytest 9."
+            ),
+            "  See: https://docs.pytest.org/en/stable/deprecations.html#sync-test-depending-on-async-fixture",
+        ]
+    )
+    result.assert_outcomes(passed=1, warnings=1)
 
 
 def test_pdb_can_be_rewritten(pytester: Pytester) -> None:
@@ -1238,8 +1427,6 @@ def test():
             "    def check():",
             ">       assert 1 == 2",
             "E       assert 1 == 2",
-            "E         +1",
-            "E         -2",
             "",
             "pdb.py:2: AssertionError",
             "*= 1 failed in *",
@@ -1270,8 +1457,7 @@ def test_simple():
     result.stderr.fnmatch_lines(["*@this is stderr@*"])
 
     # now ensure the output is in the junitxml
-    with open(pytester.path.joinpath("output.xml")) as f:
-        fullXml = f.read()
+    fullXml = pytester.path.joinpath("output.xml").read_text(encoding="utf-8")
     assert "@this is stdout@\n" in fullXml
     assert "@this is stderr@\n" in fullXml
 
@@ -1281,7 +1467,7 @@ def test_simple():
     reason="Windows raises `OSError: [Errno 22] Invalid argument` instead",
 )
 def test_no_brokenpipeerror_message(pytester: Pytester) -> None:
-    """Ensure that the broken pipe error message is supressed.
+    """Ensure that the broken pipe error message is suppressed.
 
     In some Python versions, it reaches sys.unraisablehook, in others
     a BrokenPipeError exception is propagated, but either way it prints
@@ -1295,3 +1481,122 @@ def test_no_brokenpipeerror_message(pytester: Pytester) -> None:
 
     # Cleanup.
     popen.stderr.close()
+
+
+def test_function_return_non_none_error(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+        def test_stuff():
+            return "something"
+    """
+    )
+    res = pytester.runpytest()
+    res.assert_outcomes(failed=1)
+    res.stdout.fnmatch_lines(["*Did you mean to use `assert` instead of `return`?*"])
+
+
+def test_doctest_and_normal_imports_with_importlib(pytester: Pytester) -> None:
+    """
+    Regression test for #10811: previously import_path with ImportMode.importlib would
+    not return a module if already in sys.modules, resulting in modules being imported
+    multiple times, which causes problems with modules that have import side effects.
+    """
+    # Uses the exact reproducer form #10811, given it is very minimal
+    # and illustrates the problem well.
+    pytester.makepyfile(
+        **{
+            "pmxbot/commands.py": "from . import logging",
+            "pmxbot/logging.py": "",
+            "tests/__init__.py": "",
+            "tests/test_commands.py": """
+                import importlib
+                from pmxbot import logging
+
+                class TestCommands:
+                    def test_boo(self):
+                        assert importlib.import_module('pmxbot.logging') is logging
+                """,
+        }
+    )
+    pytester.makeini(
+        """
+        [pytest]
+        addopts=
+            --doctest-modules
+            --import-mode importlib
+        """
+    )
+    result = pytester.runpytest_subprocess()
+    result.stdout.fnmatch_lines("*1 passed*")
+
+
+@pytest.mark.skip(reason="Test is not isolated")
+def test_issue_9765(pytester: Pytester) -> None:
+    """Reproducer for issue #9765 on Windows
+
+    https://github.com/pytest-dev/pytest/issues/9765
+    """
+    pytester.makepyprojecttoml(
+        """
+        [tool.pytest.ini_options]
+        addopts = "-p my_package.plugin.my_plugin"
+        """
+    )
+    pytester.makepyfile(
+        **{
+            "setup.py": (
+                """
+                from setuptools import setup
+
+                if __name__ == '__main__':
+                    setup(name='my_package', packages=['my_package', 'my_package.plugin'])
+                """
+            ),
+            "my_package/__init__.py": "",
+            "my_package/conftest.py": "",
+            "my_package/test_foo.py": "def test(): pass",
+            "my_package/plugin/__init__.py": "",
+            "my_package/plugin/my_plugin.py": (
+                """
+                import pytest
+
+                def pytest_configure(config):
+
+                    class SimplePlugin:
+                        @pytest.fixture(params=[1, 2, 3])
+                        def my_fixture(self, request):
+                            yield request.param
+
+                    config.pluginmanager.register(SimplePlugin())
+                """
+            ),
+        }
+    )
+
+    subprocess.run(
+        [sys.executable, "-Im", "pip", "install", "-e", "."],
+        check=True,
+    )
+    try:
+        # We are using subprocess.run rather than pytester.run on purpose.
+        # pytester.run is adding the current directory to PYTHONPATH which avoids
+        # the bug. We also use pytest rather than python -m pytest for the same
+        # PYTHONPATH reason.
+        subprocess.run(
+            ["pytest", "my_package"],
+            capture_output=True,
+            check=True,
+            encoding="utf-8",
+            text=True,
+        )
+    except subprocess.CalledProcessError as exc:
+        raise AssertionError(
+            f"pytest command failed:\n{exc.stdout=!s}\n{exc.stderr=!s}"
+        ) from exc
+
+
+def test_no_terminal_plugin(pytester: Pytester) -> None:
+    """Smoke test to ensure pytest can execute without the terminal plugin (#9422)."""
+    pytester.makepyfile("def test(): assert 1 == 2")
+    result = pytester.runpytest("-pno:terminal", "-s")
+    assert result.ret == ExitCode.TESTS_FAILED
diff --git a/testing/code/test_code.py b/testing/code/test_code.py
index 33809528a06..7ae5ad46100 100644
--- a/testing/code/test_code.py
+++ b/testing/code/test_code.py
@@ -1,15 +1,18 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import re
 import sys
 from types import FrameType
 from unittest import mock
 
-import pytest
 from _pytest._code import Code
 from _pytest._code import ExceptionInfo
 from _pytest._code import Frame
 from _pytest._code import Source
 from _pytest._code.code import ExceptionChainRepr
 from _pytest._code.code import ReprFuncArgs
+import pytest
 
 
 def test_ne() -> None:
diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py
index 61aa4406ad2..89088576980 100644
--- a/testing/code/test_excinfo.py
+++ b/testing/code/test_excinfo.py
@@ -1,18 +1,20 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+import fnmatch
 import importlib
 import io
 import operator
+from pathlib import Path
 import queue
+import re
 import sys
 import textwrap
-from pathlib import Path
 from typing import Any
-from typing import Dict
-from typing import Tuple
+from typing import cast
 from typing import TYPE_CHECKING
-from typing import Union
 
-import _pytest
-import pytest
+import _pytest._code
 from _pytest._code.code import ExceptionChainRepr
 from _pytest._code.code import ExceptionInfo
 from _pytest._code.code import FormattedExcinfo
@@ -22,10 +24,15 @@
 from _pytest.pathlib import import_path
 from _pytest.pytester import LineMatcher
 from _pytest.pytester import Pytester
+import pytest
 
 
 if TYPE_CHECKING:
-    from _pytest._code.code import _TracebackStyle
+    from _pytest._code.code import TracebackStyle
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import BaseExceptionGroup
+    from exceptiongroup import ExceptionGroup
 
 
 @pytest.fixture
@@ -53,6 +60,20 @@ def test_excinfo_from_exc_info_simple() -> None:
     assert info.type == ValueError
 
 
+def test_excinfo_from_exception_simple() -> None:
+    try:
+        raise ValueError
+    except ValueError as e:
+        assert e.__traceback__ is not None
+        info = _pytest._code.ExceptionInfo.from_exception(e)
+    assert info.type == ValueError
+
+
+def test_excinfo_from_exception_missing_traceback_assertion() -> None:
+    with pytest.raises(AssertionError, match=r"must have.*__traceback__"):
+        _pytest._code.ExceptionInfo.from_exception(ValueError())
+
+
 def test_excinfo_getstatement():
     def g():
         raise ValueError
@@ -162,7 +183,7 @@ def test_traceback_cut(self) -> None:
     def test_traceback_cut_excludepath(self, pytester: Pytester) -> None:
         p = pytester.makepyfile("def f(): raise ValueError")
         with pytest.raises(ValueError) as excinfo:
-            import_path(p, root=pytester.path).f()  # type: ignore[attr-defined]
+            import_path(p, root=pytester.path, consider_namespace_packages=False).f()
         basedir = Path(pytest.__file__).parent
         newtraceback = excinfo.traceback.cut(excludepath=basedir)
         for x in newtraceback:
@@ -172,7 +193,7 @@ def test_traceback_cut_excludepath(self, pytester: Pytester) -> None:
 
     def test_traceback_filter(self):
         traceback = self.excinfo.traceback
-        ntraceback = traceback.filter()
+        ntraceback = traceback.filter(self.excinfo)
         assert len(ntraceback) == len(traceback) - 1
 
     @pytest.mark.parametrize(
@@ -203,7 +224,7 @@ def h():
 
         excinfo = pytest.raises(ValueError, h)
         traceback = excinfo.traceback
-        ntraceback = traceback.filter()
+        ntraceback = traceback.filter(excinfo)
         print(f"old: {traceback!r}")
         print(f"new: {ntraceback!r}")
 
@@ -219,7 +240,7 @@ def f(n):
                 n += 1
             f(n)
 
-        excinfo = pytest.raises(RuntimeError, f, 8)
+        excinfo = pytest.raises(RecursionError, f, 8)
         traceback = excinfo.traceback
         recindex = traceback.recursionindex()
         assert recindex == 3
@@ -276,7 +297,7 @@ def fail():
         excinfo = pytest.raises(ValueError, fail)
         assert excinfo.traceback.recursionindex() is None
 
-    def test_traceback_getcrashentry(self):
+    def test_getreprcrash(self):
         def i():
             __tracebackhide__ = True
             raise ValueError
@@ -292,14 +313,13 @@ def f():
             g()
 
         excinfo = pytest.raises(ValueError, f)
-        tb = excinfo.traceback
-        entry = tb.getcrashentry()
+        reprcrash = excinfo._getreprcrash()
+        assert reprcrash is not None
         co = _pytest._code.Code.from_function(h)
-        assert entry.frame.code.path == co.path
-        assert entry.lineno == co.firstlineno + 1
-        assert entry.frame.code.name == "h"
+        assert reprcrash.path == str(co.path)
+        assert reprcrash.lineno == co.firstlineno + 1 + 1
 
-    def test_traceback_getcrashentry_empty(self):
+    def test_getreprcrash_empty(self):
         def g():
             __tracebackhide__ = True
             raise ValueError
@@ -309,12 +329,7 @@ def f():
             g()
 
         excinfo = pytest.raises(ValueError, f)
-        tb = excinfo.traceback
-        entry = tb.getcrashentry()
-        co = _pytest._code.Code.from_function(g)
-        assert entry.frame.code.path == co.path
-        assert entry.lineno == co.firstlineno + 2
-        assert entry.frame.code.name == "g"
+        assert excinfo._getreprcrash() is None
 
 
 def test_excinfo_exconly():
@@ -361,12 +376,15 @@ def test_excinfo_no_sourcecode():
     except ValueError:
         excinfo = _pytest._code.ExceptionInfo.from_current()
     s = str(excinfo.traceback[-1])
-    assert s == "  File '<string>':1 in <module>\n  ???\n"
+    # TODO: Since Python 3.13b1 under pytest-xdist, the * is `import
+    # sys;exec(eval(sys.stdin.readline()))` (execnet bootstrap code)
+    # instead of `???` like before. Is this OK?
+    fnmatch.fnmatch(s, "  File '<string>':1 in <module>\n  *\n")
 
 
 def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None:
     # XXX: simplified locally testable version
-    tmp_path.joinpath("test.txt").write_text("{{ h()}}:")
+    tmp_path.joinpath("test.txt").write_text("{{ h()}}:", encoding="utf-8")
 
     jinja2 = pytest.importorskip("jinja2")
     loader = jinja2.FileSystemLoader(str(tmp_path))
@@ -375,7 +393,7 @@ def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None:
     excinfo = pytest.raises(ValueError, template.render, h=h)
     for item in excinfo.traceback:
         print(item)  # XXX: for some reason jinja.Template.render is printed in full
-        item.source  # shouldn't fail
+        _ = item.source  # shouldn't fail
         if isinstance(item.path, Path) and item.path.name == "test.txt":
             assert str(item.source) == "{{ h()}}:"
 
@@ -406,7 +424,7 @@ def test_codepath_Queue_example() -> None:
 
 def test_match_succeeds():
     with pytest.raises(ZeroDivisionError) as excinfo:
-        0 // 0
+        _ = 0 // 0
     excinfo.match(r".*zero.*")
 
 
@@ -420,18 +438,139 @@ def test_division_zero():
             excinfo.match(r'[123]+')
     """
     )
-    result = pytester.runpytest()
+    result = pytester.runpytest("--tb=short")
     assert result.ret != 0
 
-    exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'."
-    result.stdout.fnmatch_lines([f"E * AssertionError: {exc_msg}"])
+    match = [
+        r"E .* AssertionError: Regex pattern did not match.",
+        r"E .* Regex: '\[123\]\+'",
+        r"E .* Input: 'division by zero'",
+    ]
+    result.stdout.re_match_lines(match)
     result.stdout.no_fnmatch_line("*__tracebackhide__ = True*")
 
     result = pytester.runpytest("--fulltrace")
     assert result.ret != 0
-    result.stdout.fnmatch_lines(
-        ["*__tracebackhide__ = True*", f"E * AssertionError: {exc_msg}"]
-    )
+    result.stdout.re_match_lines([r".*__tracebackhide__ = True.*", *match])
+
+
+def test_raises_accepts_generic_group() -> None:
+    with pytest.raises(ExceptionGroup[Exception]) as exc_info:
+        raise ExceptionGroup("", [RuntimeError()])
+    assert exc_info.group_contains(RuntimeError)
+
+
+def test_raises_accepts_generic_base_group() -> None:
+    with pytest.raises(BaseExceptionGroup[BaseException]) as exc_info:
+        raise ExceptionGroup("", [RuntimeError()])
+    assert exc_info.group_contains(RuntimeError)
+
+
+def test_raises_rejects_specific_generic_group() -> None:
+    with pytest.raises(ValueError):
+        pytest.raises(ExceptionGroup[RuntimeError])
+
+
+def test_raises_accepts_generic_group_in_tuple() -> None:
+    with pytest.raises((ValueError, ExceptionGroup[Exception])) as exc_info:
+        raise ExceptionGroup("", [RuntimeError()])
+    assert exc_info.group_contains(RuntimeError)
+
+
+def test_raises_exception_escapes_generic_group() -> None:
+    try:
+        with pytest.raises(ExceptionGroup[Exception]):
+            raise ValueError("my value error")
+    except ValueError as e:
+        assert str(e) == "my value error"
+    else:
+        pytest.fail("Expected ValueError to be raised")
+
+
+class TestGroupContains:
+    def test_contains_exception_type(self) -> None:
+        exc_group = ExceptionGroup("", [RuntimeError()])
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert exc_info.group_contains(RuntimeError)
+
+    def test_doesnt_contain_exception_type(self) -> None:
+        exc_group = ExceptionGroup("", [ValueError()])
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert not exc_info.group_contains(RuntimeError)
+
+    def test_contains_exception_match(self) -> None:
+        exc_group = ExceptionGroup("", [RuntimeError("exception message")])
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert exc_info.group_contains(RuntimeError, match=r"^exception message$")
+
+    def test_doesnt_contain_exception_match(self) -> None:
+        exc_group = ExceptionGroup("", [RuntimeError("message that will not match")])
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert not exc_info.group_contains(RuntimeError, match=r"^exception message$")
+
+    def test_contains_exception_type_unlimited_depth(self) -> None:
+        exc_group = ExceptionGroup("", [ExceptionGroup("", [RuntimeError()])])
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert exc_info.group_contains(RuntimeError)
+
+    def test_contains_exception_type_at_depth_1(self) -> None:
+        exc_group = ExceptionGroup("", [RuntimeError()])
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert exc_info.group_contains(RuntimeError, depth=1)
+
+    def test_doesnt_contain_exception_type_past_depth(self) -> None:
+        exc_group = ExceptionGroup("", [ExceptionGroup("", [RuntimeError()])])
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert not exc_info.group_contains(RuntimeError, depth=1)
+
+    def test_contains_exception_type_specific_depth(self) -> None:
+        exc_group = ExceptionGroup("", [ExceptionGroup("", [RuntimeError()])])
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert exc_info.group_contains(RuntimeError, depth=2)
+
+    def test_contains_exception_match_unlimited_depth(self) -> None:
+        exc_group = ExceptionGroup(
+            "", [ExceptionGroup("", [RuntimeError("exception message")])]
+        )
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert exc_info.group_contains(RuntimeError, match=r"^exception message$")
+
+    def test_contains_exception_match_at_depth_1(self) -> None:
+        exc_group = ExceptionGroup("", [RuntimeError("exception message")])
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert exc_info.group_contains(
+            RuntimeError, match=r"^exception message$", depth=1
+        )
+
+    def test_doesnt_contain_exception_match_past_depth(self) -> None:
+        exc_group = ExceptionGroup(
+            "", [ExceptionGroup("", [RuntimeError("exception message")])]
+        )
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert not exc_info.group_contains(
+            RuntimeError, match=r"^exception message$", depth=1
+        )
+
+    def test_contains_exception_match_specific_depth(self) -> None:
+        exc_group = ExceptionGroup(
+            "", [ExceptionGroup("", [RuntimeError("exception message")])]
+        )
+        with pytest.raises(ExceptionGroup) as exc_info:
+            raise exc_group
+        assert exc_info.group_contains(
+            RuntimeError, match=r"^exception message$", depth=2
+        )
 
 
 class TestFormattedExcinfo:
@@ -441,9 +580,11 @@ def importasmod(source):
             source = textwrap.dedent(source)
             modpath = tmp_path.joinpath("mod.py")
             tmp_path.joinpath("__init__.py").touch()
-            modpath.write_text(source)
+            modpath.write_text(source, encoding="utf-8")
             importlib.invalidate_caches()
-            return import_path(modpath, root=tmp_path)
+            return import_path(
+                modpath, root=tmp_path, consider_namespace_packages=False
+            )
 
         return importasmod
 
@@ -461,12 +602,30 @@ def f(x):
         assert lines[0] == "|   def f(x):"
         assert lines[1] == "        pass"
 
+    def test_repr_source_out_of_bounds(self):
+        pr = FormattedExcinfo()
+        source = _pytest._code.Source(
+            """\
+            def f(x):
+                pass
+            """
+        ).strip()
+        pr.flow_marker = "|"  # type: ignore[misc]
+
+        lines = pr.get_source(source, 100)
+        assert len(lines) == 1
+        assert lines[0] == "|   ???"
+
+        lines = pr.get_source(source, -100)
+        assert len(lines) == 1
+        assert lines[0] == "|   ???"
+
     def test_repr_source_excinfo(self) -> None:
         """Check if indentation is right."""
         try:
 
             def f():
-                1 / 0
+                _ = 1 / 0
 
             f()
 
@@ -483,7 +642,7 @@ def f():
             print(line)
         assert lines == [
             "    def f():",
-            ">       1 / 0",
+            ">       _ = 1 / 0",
             "E       ZeroDivisionError: division by zero",
         ]
 
@@ -520,7 +679,7 @@ def test_repr_source_failing_fullsource(self, monkeypatch) -> None:
         pr = FormattedExcinfo()
 
         try:
-            1 / 0
+            _ = 1 / 0
         except ZeroDivisionError:
             excinfo = ExceptionInfo.from_current()
 
@@ -588,6 +747,29 @@ def test_repr_local_truncated(self) -> None:
         assert full_reprlocals.lines
         assert full_reprlocals.lines[0] == "l          = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
 
+    def test_repr_args_not_truncated(self, importasmod) -> None:
+        mod = importasmod(
+            """
+            def func1(m):
+                raise ValueError("hello\\nworld")
+        """
+        )
+        excinfo = pytest.raises(ValueError, mod.func1, "m" * 500)
+        excinfo.traceback = excinfo.traceback.filter(excinfo)
+        entry = excinfo.traceback[-1]
+        p = FormattedExcinfo(funcargs=True, truncate_args=True)
+        reprfuncargs = p.repr_args(entry)
+        assert reprfuncargs is not None
+        arg1 = cast(str, reprfuncargs.args[0][1])
+        assert len(arg1) < 500
+        assert "..." in arg1
+        # again without truncate
+        p = FormattedExcinfo(funcargs=True, truncate_args=False)
+        reprfuncargs = p.repr_args(entry)
+        assert reprfuncargs is not None
+        assert reprfuncargs.args[0] == ("m", repr("m" * 500))
+        assert "..." not in cast(str, reprfuncargs.args[0][1])
+
     def test_repr_tracebackentry_lines(self, importasmod) -> None:
         mod = importasmod(
             """
@@ -596,7 +778,7 @@ def func1():
         """
         )
         excinfo = pytest.raises(ValueError, mod.func1)
-        excinfo.traceback = excinfo.traceback.filter()
+        excinfo.traceback = excinfo.traceback.filter(excinfo)
         p = FormattedExcinfo()
         reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
 
@@ -629,7 +811,7 @@ def func1(m, x, y, z):
         """
         )
         excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120)
-        excinfo.traceback = excinfo.traceback.filter()
+        excinfo.traceback = excinfo.traceback.filter(excinfo)
         entry = excinfo.traceback[-1]
         p = FormattedExcinfo(funcargs=True)
         reprfuncargs = p.repr_args(entry)
@@ -656,7 +838,7 @@ def func1(x, *y, **z):
         """
         )
         excinfo = pytest.raises(ValueError, mod.func1, "a", "b", c="d")
-        excinfo.traceback = excinfo.traceback.filter()
+        excinfo.traceback = excinfo.traceback.filter(excinfo)
         entry = excinfo.traceback[-1]
         p = FormattedExcinfo(funcargs=True)
         reprfuncargs = p.repr_args(entry)
@@ -701,6 +883,37 @@ def entry():
         assert basename in str(reprtb.reprfileloc.path)
         assert reprtb.reprfileloc.lineno == 3
 
+    @pytest.mark.skipif(
+        "sys.version_info < (3,11)",
+        reason="Column level traceback info added in python 3.11",
+    )
+    def test_repr_traceback_entry_short_carets(self, importasmod) -> None:
+        mod = importasmod(
+            """
+            def div_by_zero():
+                return 1 / 0
+            def func1():
+                return 42 + div_by_zero()
+            def entry():
+                func1()
+        """
+        )
+        excinfo = pytest.raises(ZeroDivisionError, mod.entry)
+        p = FormattedExcinfo(style="short")
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-3])
+        assert len(reprtb.lines) == 1
+        assert reprtb.lines[0] == "    func1()"
+
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
+        assert len(reprtb.lines) == 2
+        assert reprtb.lines[0] == "    return 42 + div_by_zero()"
+        assert reprtb.lines[1] == "                ^^^^^^^^^^^^^"
+
+        reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
+        assert len(reprtb.lines) == 2
+        assert reprtb.lines[0] == "    return 1 / 0"
+        assert reprtb.lines[1] == "           ^^^^^"
+
     def test_repr_tracebackentry_no(self, importasmod):
         mod = importasmod(
             """
@@ -737,7 +950,11 @@ def entry():
         reprtb = p.repr_traceback(excinfo)
         assert len(reprtb.reprentries) == 3
 
-    def test_traceback_short_no_source(self, importasmod, monkeypatch) -> None:
+    def test_traceback_short_no_source(
+        self,
+        importasmod,
+        monkeypatch: pytest.MonkeyPatch,
+    ) -> None:
         mod = importasmod(
             """
             def func1():
@@ -749,14 +966,14 @@ def entry():
         excinfo = pytest.raises(ValueError, mod.entry)
         from _pytest._code.code import Code
 
-        monkeypatch.setattr(Code, "path", "bogus")
-        p = FormattedExcinfo(style="short")
-        reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
-        lines = reprtb.lines
-        last_p = FormattedExcinfo(style="short")
-        last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
-        last_lines = last_reprtb.lines
-        monkeypatch.undo()
+        with monkeypatch.context() as mp:
+            mp.setattr(Code, "path", "bogus")
+            p = FormattedExcinfo(style="short")
+            reprtb = p.repr_traceback_entry(excinfo.traceback[-2])
+            lines = reprtb.lines
+            last_p = FormattedExcinfo(style="short")
+            last_reprtb = last_p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
+            last_lines = last_reprtb.lines
         assert lines[0] == "    func1()"
 
         assert last_lines[0] == '    raise ValueError("hello")'
@@ -773,7 +990,7 @@ def entry():
         )
         excinfo = pytest.raises(ValueError, mod.entry)
 
-        styles: Tuple[_TracebackStyle, ...] = ("long", "short")
+        styles: tuple[TracebackStyle, ...] = ("long", "short")
         for style in styles:
             p = FormattedExcinfo(style=style)
             reprtb = p.repr_traceback(excinfo)
@@ -812,8 +1029,8 @@ def raiseos():
             upframe = sys._getframe().f_back
             assert upframe is not None
             if upframe.f_code.co_name == "_makepath":
-                # Only raise with expected calls, but not via e.g. inspect for
-                # py38-windows.
+                # Only raise with expected calls, and not accidentally via 'inspect'
+                # See 79ae86cc3f76d69460e1c7beca4ce95e68ab80a6
                 raised += 1
                 raise OSError(2, "custom_oserror")
             return orig_path_cwd()
@@ -900,7 +1117,7 @@ def entry():
         )
         excinfo = pytest.raises(ValueError, mod.entry)
 
-        styles: Tuple[_TracebackStyle, ...] = ("short", "long", "no")
+        styles: tuple[TracebackStyle, ...] = ("short", "long", "no")
         for style in styles:
             for showlocals in (True, False):
                 repr = excinfo.getrepr(style=style, showlocals=showlocals)
@@ -930,7 +1147,7 @@ def f():
         """
         )
         excinfo = pytest.raises(ValueError, mod.f)
-        excinfo.traceback = excinfo.traceback.filter()
+        excinfo.traceback = excinfo.traceback.filter(excinfo)
         repr = excinfo.getrepr()
         repr.toterminal(tw_mock)
         assert tw_mock.lines[0] == ""
@@ -964,7 +1181,7 @@ def f():
         )
         excinfo = pytest.raises(ValueError, mod.f)
         tmp_path.joinpath("mod.py").unlink()
-        excinfo.traceback = excinfo.traceback.filter()
+        excinfo.traceback = excinfo.traceback.filter(excinfo)
         repr = excinfo.getrepr()
         repr.toterminal(tw_mock)
         assert tw_mock.lines[0] == ""
@@ -995,8 +1212,8 @@ def f():
         """
         )
         excinfo = pytest.raises(ValueError, mod.f)
-        tmp_path.joinpath("mod.py").write_text("asdf")
-        excinfo.traceback = excinfo.traceback.filter()
+        tmp_path.joinpath("mod.py").write_text("asdf", encoding="utf-8")
+        excinfo.traceback = excinfo.traceback.filter(excinfo)
         repr = excinfo.getrepr()
         repr.toterminal(tw_mock)
         assert tw_mock.lines[0] == ""
@@ -1042,6 +1259,23 @@ def f():
         line = tw_mock.lines[-1]
         assert line == ":3: ValueError"
 
+    def test_toterminal_value(self, importasmod, tw_mock):
+        mod = importasmod(
+            """
+            def g(x):
+                raise ValueError(x)
+            def f():
+                g('some_value')
+        """
+        )
+        excinfo = pytest.raises(ValueError, mod.f)
+        excinfo.traceback = excinfo.traceback.filter(excinfo)
+        repr = excinfo.getrepr(style="value")
+        repr.toterminal(tw_mock)
+
+        assert tw_mock.get_write_msg(0) == "some_value"
+        assert tw_mock.get_write_msg(1) == "\n"
+
     @pytest.mark.parametrize(
         "reproptions",
         [
@@ -1052,9 +1286,7 @@ def f():
                     "funcargs": funcargs,
                     "tbfilter": tbfilter,
                 },
-                id="style={},showlocals={},funcargs={},tbfilter={}".format(
-                    style, showlocals, funcargs, tbfilter
-                ),
+                id=f"style={style},showlocals={showlocals},funcargs={funcargs},tbfilter={tbfilter}",
             )
             for style in ["long", "short", "line", "no", "native", "value", "auto"]
             for showlocals in (True, False)
@@ -1062,7 +1294,7 @@ def f():
             for funcargs in (True, False)
         ],
     )
-    def test_format_excinfo(self, reproptions: Dict[str, Any]) -> None:
+    def test_format_excinfo(self, reproptions: dict[str, Any]) -> None:
         def bar():
             assert False, "some error"
 
@@ -1093,9 +1325,11 @@ def i():
         """
         )
         excinfo = pytest.raises(ValueError, mod.f)
-        excinfo.traceback = excinfo.traceback.filter()
-        excinfo.traceback[1].set_repr_style("short")
-        excinfo.traceback[2].set_repr_style("short")
+        excinfo.traceback = excinfo.traceback.filter(excinfo)
+        excinfo.traceback = _pytest._code.Traceback(
+            entry if i not in (1, 2) else entry.with_repr_style("short")
+            for i, entry in enumerate(excinfo.traceback)
+        )
         r = excinfo.getrepr(style="long")
         r.toterminal(tw_mock)
         for line in tw_mock.lines:
@@ -1140,7 +1374,7 @@ def g():
                 raise ValueError()
 
             def h():
-                raise AttributeError()
+                if True: raise AttributeError()
         """
         )
         excinfo = pytest.raises(AttributeError, mod.f)
@@ -1201,12 +1435,22 @@ def h():
         assert tw_mock.lines[40] == ("_ ", None)
         assert tw_mock.lines[41] == ""
         assert tw_mock.lines[42] == "    def h():"
-        assert tw_mock.lines[43] == ">       raise AttributeError()"
-        assert tw_mock.lines[44] == "E       AttributeError"
-        assert tw_mock.lines[45] == ""
-        line = tw_mock.get_write_msg(46)
-        assert line.endswith("mod.py")
-        assert tw_mock.lines[47] == ":15: AttributeError"
+        # On python 3.11 and greater, check for carets in the traceback.
+        if sys.version_info >= (3, 11):
+            assert tw_mock.lines[43] == ">       if True: raise AttributeError()"
+            assert tw_mock.lines[44] == "                 ^^^^^^^^^^^^^^^^^^^^^^"
+            assert tw_mock.lines[45] == "E       AttributeError"
+            assert tw_mock.lines[46] == ""
+            line = tw_mock.get_write_msg(47)
+            assert line.endswith("mod.py")
+            assert tw_mock.lines[48] == ":15: AttributeError"
+        else:
+            assert tw_mock.lines[43] == ">       if True: raise AttributeError()"
+            assert tw_mock.lines[44] == "E       AttributeError"
+            assert tw_mock.lines[45] == ""
+            line = tw_mock.get_write_msg(46)
+            assert line.endswith("mod.py")
+            assert tw_mock.lines[47] == ":15: AttributeError"
 
     @pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"])
     def test_exc_repr_chain_suppression(self, importasmod, mode, tw_mock):
@@ -1216,7 +1460,7 @@ def test_exc_repr_chain_suppression(self, importasmod, mode, tw_mock):
         """
         raise_suffix = " from None" if mode == "from_none" else ""
         mod = importasmod(
-            """
+            f"""
             def f():
                 try:
                     g()
@@ -1224,9 +1468,7 @@ def f():
                     raise AttributeError(){raise_suffix}
             def g():
                 raise ValueError()
-        """.format(
-                raise_suffix=raise_suffix
-            )
+        """
         )
         excinfo = pytest.raises(AttributeError, mod.f)
         r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress")
@@ -1238,9 +1480,7 @@ def g():
         assert tw_mock.lines[2] == "        try:"
         assert tw_mock.lines[3] == "            g()"
         assert tw_mock.lines[4] == "        except Exception:"
-        assert tw_mock.lines[5] == ">           raise AttributeError(){}".format(
-            raise_suffix
-        )
+        assert tw_mock.lines[5] == f">           raise AttributeError(){raise_suffix}"
         assert tw_mock.lines[6] == "E           AttributeError"
         assert tw_mock.lines[7] == ""
         line = tw_mock.get_write_msg(8)
@@ -1271,7 +1511,7 @@ def test_exc_chain_repr_without_traceback(self, importasmod, reason, description
         """
         exc_handling_code = " from e" if reason == "cause" else ""
         mod = importasmod(
-            """
+            f"""
             def f():
                 try:
                     g()
@@ -1279,16 +1519,14 @@ def f():
                     raise RuntimeError('runtime problem'){exc_handling_code}
             def g():
                 raise ValueError('invalid value')
-        """.format(
-                exc_handling_code=exc_handling_code
-            )
+        """
         )
 
         with pytest.raises(RuntimeError) as excinfo:
             mod.f()
 
         # emulate the issue described in #1984
-        attr = "__%s__" % reason
+        attr = f"__{reason}__"
         getattr(excinfo.value, attr).__traceback__ = None
 
         r = excinfo.getrepr()
@@ -1331,23 +1569,44 @@ def unreraise():
         r = excinfo.getrepr(style="short")
         r.toterminal(tw_mock)
         out = "\n".join(line for line in tw_mock.lines if isinstance(line, str))
-        expected_out = textwrap.dedent(
-            """\
-            :13: in unreraise
-                reraise()
-            :10: in reraise
-                raise Err() from e
-            E   test_exc_chain_repr_cycle0.mod.Err
-
-            During handling of the above exception, another exception occurred:
-            :15: in unreraise
-                raise e.__cause__
-            :8: in reraise
-                fail()
-            :5: in fail
-                return 0 / 0
-            E   ZeroDivisionError: division by zero"""
-        )
+        # Assert highlighting carets in python3.11+
+        if sys.version_info >= (3, 11):
+            expected_out = textwrap.dedent(
+                """\
+                :13: in unreraise
+                    reraise()
+                :10: in reraise
+                    raise Err() from e
+                E   test_exc_chain_repr_cycle0.mod.Err
+
+                During handling of the above exception, another exception occurred:
+                :15: in unreraise
+                    raise e.__cause__
+                :8: in reraise
+                    fail()
+                :5: in fail
+                    return 0 / 0
+                           ^^^^^
+                E   ZeroDivisionError: division by zero"""
+            )
+        else:
+            expected_out = textwrap.dedent(
+                """\
+                :13: in unreraise
+                    reraise()
+                :10: in reraise
+                    raise Err() from e
+                E   test_exc_chain_repr_cycle0.mod.Err
+
+                During handling of the above exception, another exception occurred:
+                :15: in unreraise
+                    raise e.__cause__
+                :8: in reraise
+                    fail()
+                :5: in fail
+                    return 0 / 0
+                E   ZeroDivisionError: division by zero"""
+            )
         assert out == expected_out
 
     def test_exec_type_error_filter(self, importasmod):
@@ -1361,14 +1620,14 @@ def f():
         with pytest.raises(TypeError) as excinfo:
             mod.f()
         # previously crashed with `AttributeError: list has no attribute get`
-        excinfo.traceback.filter()
+        excinfo.traceback.filter(excinfo)
 
 
 @pytest.mark.parametrize("style", ["short", "long"])
 @pytest.mark.parametrize("encoding", [None, "utf8", "utf16"])
 def test_repr_traceback_with_unicode(style, encoding):
     if encoding is None:
-        msg: Union[str, bytes] = "☹"
+        msg: str | bytes = "☹"
     else:
         msg = "☹".encode(encoding)
     try:
@@ -1397,7 +1656,7 @@ def test(tmp_path):
     result.stderr.no_fnmatch_line("*INTERNALERROR*")
 
 
-def test_regression_nagative_line_index(pytester: Pytester) -> None:
+def test_regression_negative_line_index(pytester: Pytester) -> None:
     """
     With Python 3.10 alphas, there was an INTERNALERROR reported in
     https://github.com/pytest-dev/pytest/pull/8227
@@ -1466,5 +1725,280 @@ def __getattr__(self, attr):
             return getattr(self, "_" + attr)
 
     with pytest.raises(RuntimeError) as excinfo:
-        RecursionDepthError().trigger
+        _ = RecursionDepthError().trigger
     assert "maximum recursion" in str(excinfo.getrepr())
+
+
+def _exceptiongroup_common(
+    pytester: Pytester,
+    outer_chain: str,
+    inner_chain: str,
+    native: bool,
+) -> None:
+    pre_raise = "exceptiongroup." if not native else ""
+    pre_catch = pre_raise if sys.version_info < (3, 11) else ""
+    filestr = f"""
+    {"import exceptiongroup" if not native else ""}
+    import pytest
+
+    def f(): raise ValueError("From f()")
+    def g(): raise BaseException("From g()")
+
+    def inner(inner_chain):
+        excs = []
+        for callback in [f, g]:
+            try:
+                callback()
+            except BaseException as err:
+                excs.append(err)
+        if excs:
+            if inner_chain == "none":
+                raise {pre_raise}BaseExceptionGroup("Oops", excs)
+            try:
+                raise SyntaxError()
+            except SyntaxError as e:
+                if inner_chain == "from":
+                    raise {pre_raise}BaseExceptionGroup("Oops", excs) from e
+                else:
+                    raise {pre_raise}BaseExceptionGroup("Oops", excs)
+
+    def outer(outer_chain, inner_chain):
+        try:
+            inner(inner_chain)
+        except {pre_catch}BaseExceptionGroup as e:
+            if outer_chain == "none":
+                raise
+            if outer_chain == "from":
+                raise IndexError() from e
+            else:
+                raise IndexError()
+
+
+    def test():
+        outer("{outer_chain}", "{inner_chain}")
+    """
+    pytester.makepyfile(test_excgroup=filestr)
+    result = pytester.runpytest()
+    match_lines = []
+    if inner_chain in ("another", "from"):
+        match_lines.append(r"SyntaxError: <no detail available>")
+
+    match_lines += [
+        r"  + Exception Group Traceback (most recent call last):",
+        rf"  \| {pre_catch}BaseExceptionGroup: Oops \(2 sub-exceptions\)",
+        r"    \| ValueError: From f\(\)",
+        r"    \| BaseException: From g\(\)",
+        r"=* short test summary info =*",
+    ]
+    if outer_chain in ("another", "from"):
+        match_lines.append(r"FAILED test_excgroup.py::test - IndexError")
+    else:
+        match_lines.append(
+            rf"FAILED test_excgroup.py::test - {pre_catch}BaseExceptionGroup: Oops \(2.*"
+        )
+    result.stdout.re_match_lines(match_lines)
+
+
+@pytest.mark.skipif(
+    sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented"
+)
+@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
+@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
+def test_native_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
+    _exceptiongroup_common(pytester, outer_chain, inner_chain, native=True)
+
+
+@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
+@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
+def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
+    # with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it
+    pytest.importorskip("exceptiongroup")
+    _exceptiongroup_common(pytester, outer_chain, inner_chain, native=False)
+
+
+def test_exceptiongroup_short_summary_info(pytester: Pytester):
+    pytester.makepyfile(
+        """
+        import sys
+
+        if sys.version_info < (3, 11):
+            from exceptiongroup import BaseExceptionGroup, ExceptionGroup
+
+        def test_base() -> None:
+            raise BaseExceptionGroup("NOT IN SUMMARY", [SystemExit("a" * 10)])
+
+        def test_nonbase() -> None:
+            raise ExceptionGroup("NOT IN SUMMARY", [ValueError("a" * 10)])
+
+        def test_nested() -> None:
+            raise ExceptionGroup(
+                "NOT DISPLAYED", [
+                    ExceptionGroup("NOT IN SUMMARY", [ValueError("a" * 10)])
+                ]
+            )
+
+        def test_multiple() -> None:
+            raise ExceptionGroup(
+                "b" * 10,
+                [
+                    ValueError("NOT IN SUMMARY"),
+                    TypeError("NOT IN SUMMARY"),
+                ]
+            )
+
+        def test_nested_multiple() -> None:
+            raise ExceptionGroup(
+                "b" * 10,
+                [
+                    ExceptionGroup(
+                        "c" * 10,
+                        [
+                            ValueError("NOT IN SUMMARY"),
+                            TypeError("NOT IN SUMMARY"),
+                        ]
+                    )
+                ]
+            )
+        """
+    )
+    # run with -vv to not truncate summary info, default width in tests is very low
+    result = pytester.runpytest("-vv")
+    assert result.ret == 1
+    backport_str = "exceptiongroup." if sys.version_info < (3, 11) else ""
+    result.stdout.fnmatch_lines(
+        [
+            "*= short test summary info =*",
+            (
+                "FAILED test_exceptiongroup_short_summary_info.py::test_base - "
+                "SystemExit('aaaaaaaaaa') [single exception in BaseExceptionGroup]"
+            ),
+            (
+                "FAILED test_exceptiongroup_short_summary_info.py::test_nonbase - "
+                "ValueError('aaaaaaaaaa') [single exception in ExceptionGroup]"
+            ),
+            (
+                "FAILED test_exceptiongroup_short_summary_info.py::test_nested - "
+                "ValueError('aaaaaaaaaa') [single exception in ExceptionGroup]"
+            ),
+            (
+                "FAILED test_exceptiongroup_short_summary_info.py::test_multiple - "
+                f"{backport_str}ExceptionGroup: bbbbbbbbbb (2 sub-exceptions)"
+            ),
+            (
+                "FAILED test_exceptiongroup_short_summary_info.py::test_nested_multiple - "
+                f"{backport_str}ExceptionGroup: bbbbbbbbbb (1 sub-exception)"
+            ),
+            "*= 5 failed in *",
+        ]
+    )
+
+
+@pytest.mark.parametrize("tbstyle", ("long", "short", "auto", "line", "native"))
+def test_all_entries_hidden(pytester: Pytester, tbstyle: str) -> None:
+    """Regression test for #10903."""
+    pytester.makepyfile(
+        """
+        def test():
+            __tracebackhide__ = True
+            1 / 0
+    """
+    )
+    result = pytester.runpytest("--tb", tbstyle)
+    assert result.ret == 1
+    if tbstyle != "line":
+        result.stdout.fnmatch_lines(["*ZeroDivisionError: division by zero"])
+    if tbstyle not in ("line", "native"):
+        result.stdout.fnmatch_lines(["All traceback entries are hidden.*"])
+
+
+def test_hidden_entries_of_chained_exceptions_are_not_shown(pytester: Pytester) -> None:
+    """Hidden entries of chained exceptions are not shown (#1904)."""
+    p = pytester.makepyfile(
+        """
+        def g1():
+            __tracebackhide__ = True
+            str.does_not_exist
+
+        def f3():
+            __tracebackhide__ = True
+            1 / 0
+
+        def f2():
+            try:
+                f3()
+            except Exception:
+                g1()
+
+        def f1():
+            __tracebackhide__ = True
+            f2()
+
+        def test():
+            f1()
+        """
+    )
+    result = pytester.runpytest(str(p), "--tb=short")
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(
+        [
+            "*.py:11: in f2",
+            "    f3()",
+            "E   ZeroDivisionError: division by zero",
+            "",
+            "During handling of the above exception, another exception occurred:",
+            "*.py:20: in test",
+            "    f1()",
+            "*.py:13: in f2",
+            "    g1()",
+            "E   AttributeError:*'does_not_exist'",
+        ],
+        consecutive=True,
+    )
+
+
+def add_note(err: BaseException, msg: str) -> None:
+    """Adds a note to an exception inplace."""
+    if sys.version_info < (3, 11):
+        err.__notes__ = [*getattr(err, "__notes__", []), msg]  # type: ignore[attr-defined]
+    else:
+        err.add_note(msg)
+
+
+@pytest.mark.parametrize(
+    "error,notes,match",
+    [
+        (Exception("test"), [], "test"),
+        (AssertionError("foo"), ["bar"], "bar"),
+        (AssertionError("foo"), ["bar", "baz"], "bar"),
+        (AssertionError("foo"), ["bar", "baz"], "baz"),
+        (ValueError("foo"), ["bar", "baz"], re.compile(r"bar\nbaz", re.MULTILINE)),
+        (ValueError("foo"), ["bar", "baz"], re.compile(r"BAZ", re.IGNORECASE)),
+    ],
+)
+def test_check_error_notes_success(
+    error: Exception, notes: list[str], match: str
+) -> None:
+    for note in notes:
+        add_note(error, note)
+
+    with pytest.raises(Exception, match=match):
+        raise error
+
+
+@pytest.mark.parametrize(
+    "error, notes, match",
+    [
+        (Exception("test"), [], "foo"),
+        (AssertionError("foo"), ["bar"], "baz"),
+        (AssertionError("foo"), ["bar"], "foo\nbaz"),
+    ],
+)
+def test_check_error_notes_failure(
+    error: Exception, notes: list[str], match: str
+) -> None:
+    for note in notes:
+        add_note(error, note)
+
+    with pytest.raises(AssertionError):
+        with pytest.raises(type(error), match=match):
+            raise error
diff --git a/testing/code/test_source.py b/testing/code/test_source.py
index 53e1bb9856b..321372d4b59 100644
--- a/testing/code/test_source.py
+++ b/testing/code/test_source.py
@@ -1,23 +1,19 @@
-# flake8: noqa
-# disable flake check on this file because some constructs are strange
-# or redundant on purpose and can't be disable on a line-by-line basis
-import ast
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import inspect
 import linecache
+from pathlib import Path
 import sys
 import textwrap
-from pathlib import Path
-from types import CodeType
 from typing import Any
-from typing import Dict
-from typing import Optional
 
-import pytest
 from _pytest._code import Code
 from _pytest._code import Frame
 from _pytest._code import getfslineno
 from _pytest._code import Source
 from _pytest.pathlib import import_path
+import pytest
 
 
 def test_source_str_function() -> None:
@@ -257,7 +253,7 @@ def g():
     assert str(g_source).strip() == "def g():\n    pass  # pragma: no cover"
 
 
-def test_getfuncsource_with_multine_string() -> None:
+def test_getfuncsource_with_multiline_string() -> None:
     def f():
         c = """while True:
     pass
@@ -297,8 +293,8 @@ def method(self):
     """
     )
     path = tmp_path.joinpath("a.py")
-    path.write_text(str(source))
-    mod: Any = import_path(path, root=tmp_path)
+    path.write_text(str(source), encoding="utf-8")
+    mod: Any = import_path(path, root=tmp_path, consider_namespace_packages=False)
     s2 = Source(mod.A)
     assert str(source).strip() == str(s2).strip()
 
@@ -332,14 +328,13 @@ def test_findsource(monkeypatch) -> None:
     lines = ["if 1:\n", "    def x():\n", "          pass\n"]
     co = compile("".join(lines), filename, "exec")
 
-    # Type ignored because linecache.cache is private.
-    monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename))  # type: ignore[attr-defined]
+    monkeypatch.setitem(linecache.cache, filename, (1, None, lines, filename))
 
     src, lineno = findsource(co)
     assert src is not None
     assert "if 1:" in str(src)
 
-    d: Dict[str, Any] = {}
+    d: dict[str, Any] = {}
     eval(co, d)
     src, lineno = findsource(d["x"])
     assert src is not None
@@ -373,7 +368,11 @@ class B:
         pass
 
     B.__name__ = B.__qualname__ = "B2"
-    assert getfslineno(B)[1] == -1
+    # Since Python 3.13 this started working.
+    if sys.version_info >= (3, 13):
+        assert getfslineno(B)[1] != -1
+    else:
+        assert getfslineno(B)[1] == -1
 
 
 def test_code_of_object_instance_with_call() -> None:
@@ -443,14 +442,9 @@ def test_comments() -> None:
 '''
     for line in range(2, 6):
         assert str(getstatement(line, source)) == "    x = 1"
-    if sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"):
-        tqs_start = 8
-    else:
-        tqs_start = 10
-        assert str(getstatement(10, source)) == '"""'
-    for line in range(6, tqs_start):
+    for line in range(6, 8):
         assert str(getstatement(line, source)) == "    assert False"
-    for line in range(tqs_start, 10):
+    for line in range(8, 10):
         assert str(getstatement(line, source)) == '"""\ncomment 4\n"""'
 
 
@@ -468,7 +462,6 @@ def test_comment_in_statement() -> None:
 
 def test_source_with_decorator() -> None:
     """Test behavior with Source / Code().source with regard to decorators."""
-    from _pytest.compat import get_real_func
 
     @pytest.mark.foo
     def deco_mark():
@@ -482,14 +475,14 @@ def deco_mark():
     def deco_fixture():
         assert False
 
-    src = inspect.getsource(deco_fixture)
+    src = inspect.getsource(deco_fixture._get_wrapped_function())
     assert src == "    @pytest.fixture\n    def deco_fixture():\n        assert False\n"
-    # currenly Source does not unwrap decorators, testing the
-    # existing behavior here for explicitness, but perhaps we should revisit/change this
-    # in the future
-    assert str(Source(deco_fixture)).startswith("@functools.wraps(function)")
+    # Make sure the decorator is not a wrapped function
+    assert not str(Source(deco_fixture)).startswith("@functools.wraps(function)")
     assert (
-        textwrap.indent(str(Source(get_real_func(deco_fixture))), "    ") + "\n" == src
+        textwrap.indent(str(Source(deco_fixture._get_wrapped_function())), "    ")
+        + "\n"
+        == src
     )
 
 
@@ -618,6 +611,19 @@ def something():
     assert str(source) == "def func(): raise ValueError(42)"
 
 
+def test_decorator() -> None:
+    s = """\
+def foo(f):
+    pass
+
+@foo
+def bar():
+    pass
+    """
+    source = getstatement(3, s)
+    assert "@foo" in str(source)
+
+
 def XXX_test_expression_multiline() -> None:
     source = """\
 something
diff --git a/testing/conftest.py b/testing/conftest.py
index 107aad86b25..251b430e9cd 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -1,10 +1,17 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Generator
+import importlib.metadata
 import re
 import sys
-from typing import List
 
-import pytest
+from packaging.version import Version
+
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
+import pytest
+
 
 if sys.gettrace():
 
@@ -20,11 +27,31 @@ def restore_tracing():
             sys.settrace(orig_trace)
 
 
-@pytest.hookimpl(hookwrapper=True, tryfirst=True)
-def pytest_collection_modifyitems(items):
+@pytest.fixture(autouse=True)
+def set_column_width(monkeypatch: pytest.MonkeyPatch) -> None:
+    """
+    Force terminal width to 80: some tests check the formatting of --help, which is sensible
+    to terminal width.
+    """
+    monkeypatch.setenv("COLUMNS", "80")
+
+
+@pytest.fixture(autouse=True)
+def reset_colors(monkeypatch: pytest.MonkeyPatch) -> None:
+    """
+    Reset all color-related variables to prevent them from affecting internal pytest output
+    in tests that depend on it.
+    """
+    monkeypatch.delenv("PY_COLORS", raising=False)
+    monkeypatch.delenv("NO_COLOR", raising=False)
+    monkeypatch.delenv("FORCE_COLOR", raising=False)
+
+
+@pytest.hookimpl(wrapper=True, tryfirst=True)
+def pytest_collection_modifyitems(items) -> Generator[None]:
     """Prefer faster tests.
 
-    Use a hookwrapper to do this in the beginning, so e.g. --ff still works
+    Use a hook wrapper to do this in the beginning, so e.g. --ff still works
     correctly.
     """
     fast_items = []
@@ -61,7 +88,7 @@ def pytest_collection_modifyitems(items):
 
     items[:] = fast_items + neutral_items + slow_items + slowest_items
 
-    yield
+    return (yield)
 
 
 @pytest.fixture
@@ -94,8 +121,8 @@ def markup(self, text, **kw):
             return text
 
         def get_write_msg(self, idx):
-            flag, msg = self.lines[idx]
-            assert flag == TWMock.WRITE
+            assert self.lines[idx][0] == TWMock.WRITE
+            msg = self.lines[idx][1]
             return msg
 
         fullwidth = 80
@@ -104,7 +131,7 @@ def get_write_msg(self, idx):
 
 
 @pytest.fixture
-def dummy_yaml_custom_test(pytester: Pytester):
+def dummy_yaml_custom_test(pytester: Pytester) -> None:
     """Writes a conftest file that collects and executes a dummy yaml test.
 
     Taken from the docs, but stripped down to the bare minimum, useful for
@@ -143,38 +170,52 @@ def color_mapping():
 
     Used by tests which check the actual colors output by pytest.
     """
+    # https://github.com/pygments/pygments/commit/d24e272894a56a98b1b718d9ac5fabc20124882a
+    pygments_version = Version(importlib.metadata.version("pygments"))
+    pygments_has_kwspace_hl = pygments_version >= Version("2.19")
 
     class ColorMapping:
         COLORS = {
             "red": "\x1b[31m",
             "green": "\x1b[32m",
             "yellow": "\x1b[33m",
+            "light-gray": "\x1b[90m",
+            "light-red": "\x1b[91m",
+            "light-green": "\x1b[92m",
             "bold": "\x1b[1m",
             "reset": "\x1b[0m",
             "kw": "\x1b[94m",
+            "kwspace": "\x1b[90m \x1b[39;49;00m" if pygments_has_kwspace_hl else " ",
             "hl-reset": "\x1b[39;49;00m",
             "function": "\x1b[92m",
             "number": "\x1b[94m",
             "str": "\x1b[33m",
             "print": "\x1b[96m",
+            "endline": "\x1b[90m\x1b[39;49;00m",
         }
         RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()}
+        NO_COLORS = {k: "" for k in COLORS.keys()}
 
         @classmethod
-        def format(cls, lines: List[str]) -> List[str]:
+        def format(cls, lines: list[str]) -> list[str]:
             """Straightforward replacement of color names to their ASCII codes."""
             return [line.format(**cls.COLORS) for line in lines]
 
         @classmethod
-        def format_for_fnmatch(cls, lines: List[str]) -> List[str]:
+        def format_for_fnmatch(cls, lines: list[str]) -> list[str]:
             """Replace color names for use with LineMatcher.fnmatch_lines"""
             return [line.format(**cls.COLORS).replace("[", "[[]") for line in lines]
 
         @classmethod
-        def format_for_rematch(cls, lines: List[str]) -> List[str]:
+        def format_for_rematch(cls, lines: list[str]) -> list[str]:
             """Replace color names for use with LineMatcher.re_match_lines"""
             return [line.format(**cls.RE_COLORS) for line in lines]
 
+        @classmethod
+        def strip_colors(cls, lines: list[str]) -> list[str]:
+            """Entirely remove every color code"""
+            return [line.format(**cls.NO_COLORS) for line in lines]
+
     return ColorMapping
 
 
@@ -191,26 +232,8 @@ def mock_timing(monkeypatch: MonkeyPatch):
     Time is static, and only advances through `sleep` calls, thus tests might sleep over large
     numbers and obtain accurate time() calls at the end, making tests reliable and instant.
     """
-    import attr
-
-    @attr.s
-    class MockTiming:
-
-        _current_time = attr.ib(default=1590150050.0)
-
-        def sleep(self, seconds):
-            self._current_time += seconds
-
-        def time(self):
-            return self._current_time
-
-        def patch(self):
-            from _pytest import timing
-
-            monkeypatch.setattr(timing, "sleep", self.sleep)
-            monkeypatch.setattr(timing, "time", self.time)
-            monkeypatch.setattr(timing, "perf_counter", self.time)
+    from _pytest.timing import MockTiming
 
     result = MockTiming()
-    result.patch()
+    result.patch(monkeypatch)
     return result
diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py
index 7d7e6d31240..5d0e69c58c1 100644
--- a/testing/deprecated_test.py
+++ b/testing/deprecated_test.py
@@ -1,23 +1,17 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from pathlib import Path
 import re
 import sys
-import warnings
-from pathlib import Path
-from unittest import mock
 
-import pytest
 from _pytest import deprecated
 from _pytest.compat import legacy_path
 from _pytest.pytester import Pytester
+import pytest
 from pytest import PytestDeprecationWarning
 
 
-@pytest.mark.parametrize("attribute", pytest.collect.__all__)  # type: ignore
-# false positive due to dynamic attribute
-def test_pytest_collect_module_deprecated(attribute) -> None:
-    with pytest.warns(DeprecationWarning, match=attribute):
-        getattr(pytest.collect, attribute)
-
-
 @pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS))
 @pytest.mark.filterwarnings("default")
 def test_external_plugins_integrated(pytester: Pytester, plugin) -> None:
@@ -28,96 +22,52 @@ def test_external_plugins_integrated(pytester: Pytester, plugin) -> None:
         pytester.parseconfig("-p", plugin)
 
 
-def test_fillfuncargs_is_deprecated() -> None:
-    with pytest.warns(
-        pytest.PytestDeprecationWarning,
-        match=re.escape(
-            "pytest._fillfuncargs() is deprecated, use "
-            "function._request._fillfixtures() instead if you cannot avoid reaching into internals."
-        ),
-    ):
-        pytest._fillfuncargs(mock.Mock())
-
+def test_hookspec_via_function_attributes_are_deprecated():
+    from _pytest.config import PytestPluginManager
 
-def test_fillfixtures_is_deprecated() -> None:
-    import _pytest.fixtures
-
-    with pytest.warns(
-        pytest.PytestDeprecationWarning,
-        match=re.escape(
-            "_pytest.fixtures.fillfixtures() is deprecated, use "
-            "function._request._fillfixtures() instead if you cannot avoid reaching into internals."
-        ),
-    ):
-        _pytest.fixtures.fillfixtures(mock.Mock())
+    pm = PytestPluginManager()
 
+    class DeprecatedHookMarkerSpec:
+        def pytest_bad_hook(self):
+            pass
 
-def test_minus_k_dash_is_deprecated(pytester: Pytester) -> None:
-    threepass = pytester.makepyfile(
-        test_threepass="""
-        def test_one(): assert 1
-        def test_two(): assert 1
-        def test_three(): assert 1
-    """
-    )
-    result = pytester.runpytest("-k=-test_two", threepass)
-    result.stdout.fnmatch_lines(["*The `-k '-expr'` syntax*deprecated*"])
-
-
-def test_minus_k_colon_is_deprecated(pytester: Pytester) -> None:
-    threepass = pytester.makepyfile(
-        test_threepass="""
-        def test_one(): assert 1
-        def test_two(): assert 1
-        def test_three(): assert 1
-    """
-    )
-    result = pytester.runpytest("-k", "test_two:", threepass)
-    result.stdout.fnmatch_lines(["*The `-k 'expr:'` syntax*deprecated*"])
-
+        pytest_bad_hook.historic = False  # type: ignore[attr-defined]
 
-def test_fscollector_gethookproxy_isinitpath(pytester: Pytester) -> None:
-    module = pytester.getmodulecol(
-        """
-        def test_foo(): pass
-        """,
-        withinit=True,
+    with pytest.warns(
+        PytestDeprecationWarning,
+        match=r"Please use the pytest\.hookspec\(historic=False\) decorator",
+    ) as recorder:
+        pm.add_hookspecs(DeprecatedHookMarkerSpec)
+    (record,) = recorder
+    assert (
+        record.lineno
+        == DeprecatedHookMarkerSpec.pytest_bad_hook.__code__.co_firstlineno
     )
-    assert isinstance(module, pytest.Module)
-    package = module.parent
-    assert isinstance(package, pytest.Package)
+    assert record.filename == __file__
 
-    with pytest.warns(pytest.PytestDeprecationWarning, match="gethookproxy"):
-        package.gethookproxy(pytester.path)
 
-    with pytest.warns(pytest.PytestDeprecationWarning, match="isinitpath"):
-        package.isinitpath(pytester.path)
+def test_hookimpl_via_function_attributes_are_deprecated():
+    from _pytest.config import PytestPluginManager
 
-    # The methods on Session are *not* deprecated.
-    session = module.session
-    with warnings.catch_warnings(record=True) as rec:
-        session.gethookproxy(pytester.path)
-        session.isinitpath(pytester.path)
-    assert len(rec) == 0
+    pm = PytestPluginManager()
 
+    class DeprecatedMarkImplPlugin:
+        def pytest_runtest_call(self):
+            pass
 
-def test_strict_option_is_deprecated(pytester: Pytester) -> None:
-    """--strict is a deprecated alias to --strict-markers (#7530)."""
-    pytester.makepyfile(
-        """
-        import pytest
+        pytest_runtest_call.tryfirst = True  # type: ignore[attr-defined]
 
-        @pytest.mark.unknown
-        def test_foo(): pass
-        """
-    )
-    result = pytester.runpytest("--strict")
-    result.stdout.fnmatch_lines(
-        [
-            "'unknown' not found in `markers` configuration option",
-            "*PytestRemovedIn8Warning: The --strict option is deprecated, use --strict-markers instead.",
-        ]
+    with pytest.warns(
+        PytestDeprecationWarning,
+        match=r"Please use the pytest.hookimpl\(tryfirst=True\)",
+    ) as recorder:
+        pm.register(DeprecatedMarkImplPlugin())
+    (record,) = recorder
+    assert (
+        record.lineno
+        == DeprecatedMarkImplPlugin.pytest_runtest_call.__code__.co_firstlineno
     )
+    assert record.filename == __file__
 
 
 def test_yield_fixture_is_deprecated() -> None:
@@ -142,23 +92,6 @@ def __init__(self, foo: int, *, _ispytest: bool = False) -> None:
     PrivateInit(10, _ispytest=True)
 
 
-def test_raising_unittest_skiptest_during_collection_is_deprecated(
-    pytester: Pytester,
-) -> None:
-    pytester.makepyfile(
-        """
-        import unittest
-        raise unittest.SkipTest()
-        """
-    )
-    result = pytester.runpytest()
-    result.stdout.fnmatch_lines(
-        [
-            "*PytestRemovedIn8Warning: Raising unittest.SkipTest*",
-        ]
-    )
-
-
 @pytest.mark.parametrize("hooktype", ["hook", "ihook"])
 def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request):
     path = legacy_path(tmp_path)
@@ -190,119 +123,99 @@ def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request):
             )
 
 
-def test_warns_none_is_deprecated():
-    with pytest.warns(
-        PytestDeprecationWarning,
-        match=re.escape(
-            "Passing None to catch any warning has been deprecated, pass no arguments instead:\n "
-            "Replace pytest.warns(None) by simply pytest.warns()."
-        ),
-    ):
-        with pytest.warns(None):  # type: ignore[call-overload]
-            pass
+def test_hookimpl_warnings_for_pathlib() -> None:
+    class Plugin:
+        def pytest_ignore_collect(self, path: object) -> None:
+            raise NotImplementedError()
 
+        def pytest_collect_file(self, path: object) -> None:
+            raise NotImplementedError()
 
-class TestSkipMsgArgumentDeprecated:
-    def test_skip_with_msg_is_deprecated(self, pytester: Pytester) -> None:
-        p = pytester.makepyfile(
-            """
-            import pytest
+        def pytest_pycollect_makemodule(self, path: object) -> None:
+            raise NotImplementedError()
 
-            def test_skipping_msg():
-                pytest.skip(msg="skippedmsg")
-            """
-        )
-        result = pytester.runpytest(p)
-        result.stdout.fnmatch_lines(
-            [
-                "*PytestRemovedIn8Warning: pytest.skip(msg=...) is now deprecated, "
-                "use pytest.skip(reason=...) instead",
-                '*pytest.skip(msg="skippedmsg")*',
-            ]
-        )
-        result.assert_outcomes(skipped=1, warnings=1)
+        def pytest_report_header(self, startdir: object) -> str:
+            raise NotImplementedError()
 
-    def test_fail_with_msg_is_deprecated(self, pytester: Pytester) -> None:
-        p = pytester.makepyfile(
-            """
-            import pytest
+        def pytest_report_collectionfinish(self, startdir: object) -> str:
+            raise NotImplementedError()
 
-            def test_failing_msg():
-                pytest.fail(msg="failedmsg")
-            """
-        )
-        result = pytester.runpytest(p)
-        result.stdout.fnmatch_lines(
-            [
-                "*PytestRemovedIn8Warning: pytest.fail(msg=...) is now deprecated, "
-                "use pytest.fail(reason=...) instead",
-                '*pytest.fail(msg="failedmsg")',
-            ]
-        )
-        result.assert_outcomes(failed=1, warnings=1)
-
-    def test_exit_with_msg_is_deprecated(self, pytester: Pytester) -> None:
-        p = pytester.makepyfile(
-            """
-            import pytest
-
-            def test_exit_msg():
-                pytest.exit(msg="exitmsg")
-            """
-        )
-        result = pytester.runpytest(p)
-        result.stdout.fnmatch_lines(
-            [
-                "*PytestRemovedIn8Warning: pytest.exit(msg=...) is now deprecated, "
-                "use pytest.exit(reason=...) instead",
-            ]
-        )
-        result.assert_outcomes(warnings=1)
-
-
-def test_deprecation_of_cmdline_preparse(pytester: Pytester) -> None:
-    pytester.makeconftest(
-        """
-        def pytest_cmdline_preparse(config, args):
-            ...
-
-        """
-    )
-    result = pytester.runpytest()
-    result.stdout.fnmatch_lines(
-        [
-            "*PytestRemovedIn8Warning: The pytest_cmdline_preparse hook is deprecated*",
-            "*Please use pytest_load_initial_conftests hook instead.*",
-        ]
-    )
+    pm = pytest.PytestPluginManager()
+    with pytest.warns(
+        pytest.PytestRemovedIn9Warning,
+        match=r"py\.path\.local.* argument is deprecated",
+    ) as wc:
+        pm.register(Plugin())
+    assert len(wc.list) == 5
 
 
 def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None:
     mod = pytester.getmodulecol("")
 
+    class MyFile(pytest.File):
+        def collect(self):
+            raise NotImplementedError()
+
     with pytest.warns(
         pytest.PytestDeprecationWarning,
-        match=re.escape("The (fspath: py.path.local) argument to File is deprecated."),
+        match=re.escape(
+            "The (fspath: py.path.local) argument to MyFile is deprecated."
+        ),
     ):
-        pytest.File.from_parent(
+        MyFile.from_parent(
             parent=mod.parent,
             fspath=legacy_path("bla"),
         )
 
 
-@pytest.mark.skipif(
-    sys.version_info < (3, 7),
-    reason="This deprecation can only be emitted on python>=3.7",
-)
-def test_importing_instance_is_deprecated(pytester: Pytester) -> None:
+def test_fixture_disallow_on_marked_functions():
+    """Test that applying @pytest.fixture to a marked function warns (#3364)."""
     with pytest.warns(
-        pytest.PytestDeprecationWarning,
-        match=re.escape("The pytest.Instance collector type is deprecated"),
-    ):
-        pytest.Instance
+        pytest.PytestRemovedIn9Warning,
+        match=r"Marks applied to fixtures have no effect",
+    ) as record:
+
+        @pytest.fixture
+        @pytest.mark.parametrize("example", ["hello"])
+        @pytest.mark.usefixtures("tmp_path")
+        def foo():
+            raise NotImplementedError()
+
+    # it's only possible to get one warning here because you're already prevented
+    # from applying @fixture twice
+    # ValueError("fixture is being applied more than once to the same function")
+    assert len(record) == 1
+
 
+def test_fixture_disallow_marks_on_fixtures():
+    """Test that applying a mark to a fixture warns (#3364)."""
     with pytest.warns(
-        pytest.PytestDeprecationWarning,
-        match=re.escape("The pytest.Instance collector type is deprecated"),
-    ):
-        from _pytest.python import Instance  # noqa: F401
+        pytest.PytestRemovedIn9Warning,
+        match=r"Marks applied to fixtures have no effect",
+    ) as record:
+
+        @pytest.mark.parametrize("example", ["hello"])
+        @pytest.mark.usefixtures("tmp_path")
+        @pytest.fixture
+        def foo():
+            raise NotImplementedError()
+
+    assert len(record) == 2  # one for each mark decorator
+    # should point to this file
+    assert all(rec.filename == __file__ for rec in record)
+
+
+def test_fixture_disallowed_between_marks():
+    """Test that applying a mark to a fixture warns (#3364)."""
+    with pytest.warns(
+        pytest.PytestRemovedIn9Warning,
+        match=r"Marks applied to fixtures have no effect",
+    ) as record:
+
+        @pytest.mark.parametrize("example", ["hello"])
+        @pytest.fixture
+        @pytest.mark.usefixtures("tmp_path")
+        def foo():
+            raise NotImplementedError()
+
+    assert len(record) == 2  # one for each mark decorator
diff --git a/testing/example_scripts/acceptance/fixture_mock_integration.py b/testing/example_scripts/acceptance/fixture_mock_integration.py
index 5b00ac90e1b..e612ae01e66 100644
--- a/testing/example_scripts/acceptance/fixture_mock_integration.py
+++ b/testing/example_scripts/acceptance/fixture_mock_integration.py
@@ -1,8 +1,13 @@
+# mypy: allow-untyped-defs
 """Reproduces issue #3774"""
+
+from __future__ import annotations
+
 from unittest import mock
 
 import pytest
 
+
 config = {"mykey": "ORIGINAL"}
 
 
diff --git a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py
index 9cd366295e7..5e30bb15883 100644
--- a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py
+++ b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_init():
     pass
diff --git a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py
index 8f2d73cfa4f..3cb8f1be095 100644
--- a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py
+++ b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_foo():
     pass
diff --git a/testing/example_scripts/collect/package_infinite_recursion/conftest.py b/testing/example_scripts/collect/package_infinite_recursion/conftest.py
index 973ccc0c030..c2d2b918874 100644
--- a/testing/example_scripts/collect/package_infinite_recursion/conftest.py
+++ b/testing/example_scripts/collect/package_infinite_recursion/conftest.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def pytest_ignore_collect(collection_path):
     return False
diff --git a/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py b/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py
index f174823854e..38c51e586fc 100644
--- a/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py
+++ b/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test():
     pass
diff --git a/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py b/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py
index e69de29bb2d..5e30bb15883 100644
--- a/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py
+++ b/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py
@@ -0,0 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
+def test_init():
+    pass
diff --git a/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py b/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py
index f174823854e..3cb8f1be095 100644
--- a/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py
+++ b/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py
@@ -1,2 +1,6 @@
-def test():
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
+def test_foo():
     pass
diff --git a/testing/example_scripts/config/collect_pytest_prefix/conftest.py b/testing/example_scripts/config/collect_pytest_prefix/conftest.py
index 2da4ffe2fed..5e0ab54411b 100644
--- a/testing/example_scripts/config/collect_pytest_prefix/conftest.py
+++ b/testing/example_scripts/config/collect_pytest_prefix/conftest.py
@@ -1,2 +1,5 @@
+from __future__ import annotations
+
+
 class pytest_something:
     pass
diff --git a/testing/example_scripts/config/collect_pytest_prefix/test_foo.py b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py
index 8f2d73cfa4f..3cb8f1be095 100644
--- a/testing/example_scripts/config/collect_pytest_prefix/test_foo.py
+++ b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_foo():
     pass
diff --git a/testing/example_scripts/conftest_usageerror/conftest.py b/testing/example_scripts/conftest_usageerror/conftest.py
index 8973e4252d3..a6690bdc303 100644
--- a/testing/example_scripts/conftest_usageerror/conftest.py
+++ b/testing/example_scripts/conftest_usageerror/conftest.py
@@ -1,3 +1,7 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def pytest_configure(config):
     import pytest
 
diff --git a/testing/example_scripts/customdirectory/conftest.py b/testing/example_scripts/customdirectory/conftest.py
new file mode 100644
index 00000000000..4718d7d5be3
--- /dev/null
+++ b/testing/example_scripts/customdirectory/conftest.py
@@ -0,0 +1,25 @@
+# mypy: allow-untyped-defs
+# content of conftest.py
+from __future__ import annotations
+
+import json
+
+import pytest
+
+
+class ManifestDirectory(pytest.Directory):
+    def collect(self):
+        manifest_path = self.path / "manifest.json"
+        manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
+        ihook = self.ihook
+        for file in manifest["files"]:
+            yield from ihook.pytest_collect_file(
+                file_path=self.path / file, parent=self
+            )
+
+
+@pytest.hookimpl
+def pytest_collect_directory(path, parent):
+    if path.joinpath("manifest.json").is_file():
+        return ManifestDirectory.from_parent(parent=parent, path=path)
+    return None
diff --git a/testing/example_scripts/customdirectory/pytest.ini b/testing/example_scripts/customdirectory/pytest.ini
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/testing/example_scripts/customdirectory/tests/manifest.json b/testing/example_scripts/customdirectory/tests/manifest.json
new file mode 100644
index 00000000000..6ab6d0a5222
--- /dev/null
+++ b/testing/example_scripts/customdirectory/tests/manifest.json
@@ -0,0 +1,6 @@
+{
+    "files": [
+        "test_first.py",
+        "test_second.py"
+    ]
+}
diff --git a/testing/example_scripts/customdirectory/tests/test_first.py b/testing/example_scripts/customdirectory/tests/test_first.py
new file mode 100644
index 00000000000..06f40ca4733
--- /dev/null
+++ b/testing/example_scripts/customdirectory/tests/test_first.py
@@ -0,0 +1,7 @@
+# mypy: allow-untyped-defs
+# content of test_first.py
+from __future__ import annotations
+
+
+def test_1():
+    pass
diff --git a/testing/example_scripts/customdirectory/tests/test_second.py b/testing/example_scripts/customdirectory/tests/test_second.py
new file mode 100644
index 00000000000..79bcc099e65
--- /dev/null
+++ b/testing/example_scripts/customdirectory/tests/test_second.py
@@ -0,0 +1,7 @@
+# mypy: allow-untyped-defs
+# content of test_second.py
+from __future__ import annotations
+
+
+def test_2():
+    pass
diff --git a/testing/example_scripts/customdirectory/tests/test_third.py b/testing/example_scripts/customdirectory/tests/test_third.py
new file mode 100644
index 00000000000..5af476ad44d
--- /dev/null
+++ b/testing/example_scripts/customdirectory/tests/test_third.py
@@ -0,0 +1,7 @@
+# mypy: allow-untyped-defs
+# content of test_third.py
+from __future__ import annotations
+
+
+def test_3():
+    pass
diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_dataclasses.py
index d96c90a91bd..18180b99f2d 100644
--- a/testing/example_scripts/dataclasses/test_compare_dataclasses.py
+++ b/testing/example_scripts/dataclasses/test_compare_dataclasses.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from dataclasses import dataclass
 from dataclasses import field
 
diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py
index 7479c66c1be..0dcc7ab2802 100644
--- a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py
+++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from dataclasses import dataclass
 from dataclasses import field
 
diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py
index 4737ef904e0..4985c69ff30 100644
--- a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py
+++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from dataclasses import dataclass
 from dataclasses import field
 
diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py
new file mode 100644
index 00000000000..5ae9a02f99b
--- /dev/null
+++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+from dataclasses import field
+
+
+def test_dataclasses() -> None:
+    @dataclass
+    class SimpleDataObject:
+        field_a: int = field()
+        field_b: str = field()
+
+        def __eq__(self, o: object, /) -> bool:
+            return super().__eq__(o)
+
+    left = SimpleDataObject(1, "b")
+    right = SimpleDataObject(1, "c")
+
+    assert left == right
diff --git a/testing/example_scripts/dataclasses/test_compare_initvar.py b/testing/example_scripts/dataclasses/test_compare_initvar.py
new file mode 100644
index 00000000000..fc589e1fde4
--- /dev/null
+++ b/testing/example_scripts/dataclasses/test_compare_initvar.py
@@ -0,0 +1,15 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from dataclasses import dataclass
+from dataclasses import InitVar
+
+
+@dataclass
+class Foo:
+    init_only: InitVar[int]
+    real_attr: int
+
+
+def test_demonstrate():
+    assert Foo(1, 2) == Foo(1, 3)
diff --git a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py
index 0945790f004..885edd7d9d7 100644
--- a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py
+++ b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from dataclasses import dataclass
 
 
diff --git a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py
index 0a4820c69ba..b45a6772c59 100644
--- a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py
+++ b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from dataclasses import dataclass
 from dataclasses import field
 
diff --git a/testing/example_scripts/doctest/main_py/__main__.py b/testing/example_scripts/doctest/main_py/__main__.py
index e471d06d643..3a0f6bed1d6 100644
--- a/testing/example_scripts/doctest/main_py/__main__.py
+++ b/testing/example_scripts/doctest/main_py/__main__.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_this_is_ignored():
     assert True
diff --git a/testing/example_scripts/doctest/main_py/test_normal_module.py b/testing/example_scripts/doctest/main_py/test_normal_module.py
index 700cc9750cf..8c150da5c02 100644
--- a/testing/example_scripts/doctest/main_py/test_normal_module.py
+++ b/testing/example_scripts/doctest/main_py/test_normal_module.py
@@ -1,3 +1,7 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_doc():
     """
     >>> 10 > 5
diff --git a/testing/example_scripts/fixtures/custom_item/conftest.py b/testing/example_scripts/fixtures/custom_item/conftest.py
index a7a5e9db80a..274ab97d01b 100644
--- a/testing/example_scripts/fixtures/custom_item/conftest.py
+++ b/testing/example_scripts/fixtures/custom_item/conftest.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/custom_item/foo/test_foo.py b/testing/example_scripts/fixtures/custom_item/foo/test_foo.py
index f174823854e..38c51e586fc 100644
--- a/testing/example_scripts/fixtures/custom_item/foo/test_foo.py
+++ b/testing/example_scripts/fixtures/custom_item/foo/test_foo.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test():
     pass
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py
index be5adbeb6e5..94eaa3e0796 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py
index df36da1369b..cb3f9fbf469 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_1(arg1):
     pass
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py
index 00981c5dc12..112d1e05f27 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py
index 1c34f94acc4..3dea97f544c 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_2(arg2):
     pass
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py
index d1efcbb338c..d90961ae3c4 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py
index 5dfd2f77957..b4fcc17bfc7 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py
index 4e22ce5a137..b933b70edf3 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py
index 0d891fbb503..d31ab971f2b 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_spam(spam):
     assert spam == "spamspam"
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py
index 5dfd2f77957..b4fcc17bfc7 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py
index 46d1446f470..2d6d7faef61 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py
index 87a0c894111..45e5deaafea 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py
index 0661cb301fc..1c7a710cd0c 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py
index 256b92a17dd..96f0cacfafd 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py
index e15dbd2ca45..b78ca04b3ab 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py
index b775203231f..0dd782e4285 100644
--- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py
+++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/test_fixture_named_request.py b/testing/example_scripts/fixtures/test_fixture_named_request.py
index 75514bf8b8c..db88bcdabb9 100644
--- a/testing/example_scripts/fixtures/test_fixture_named_request.py
+++ b/testing/example_scripts/fixtures/test_fixture_named_request.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py b/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py
index 055a1220b1c..0559905cea4 100644
--- a/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py
+++ b/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py
index cb8f5d671ea..2e88c5ad5a9 100644
--- a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py
+++ b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
@@ -11,4 +14,5 @@ def pytest_collect_file(file_path, parent):
 
 
 class MyItem(pytest.Item):
-    pass
+    def runtest(self):
+        raise NotImplementedError()
diff --git a/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py b/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py
index 56444d14748..b10f874e78d 100644
--- a/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py
+++ b/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_hello():
     pass
diff --git a/testing/example_scripts/issue_519.py b/testing/example_scripts/issue_519.py
index e44367fca04..da5f5ad6aa9 100644
--- a/testing/example_scripts/issue_519.py
+++ b/testing/example_scripts/issue_519.py
@@ -1,6 +1,7 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pprint
-from typing import List
-from typing import Tuple
 
 import pytest
 
@@ -15,7 +16,7 @@ def pytest_generate_tests(metafunc):
 
 @pytest.fixture(scope="session")
 def checked_order():
-    order: List[Tuple[str, str, str]] = []
+    order: list[tuple[str, str, str]] = []
 
     yield order
     pprint.pprint(order)
diff --git a/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py b/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py
index 35a2c7b7628..c98e58316eb 100644
--- a/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py
+++ b/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py
index ff1eaf7d6bb..3b580aa341a 100644
--- a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py
+++ b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py
@@ -1,6 +1,10 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import argparse
 import pathlib
 
+
 HERE = pathlib.Path(__file__).parent
 TEST_CONTENT = (HERE / "template_test.py").read_bytes()
 
diff --git a/testing/example_scripts/perf_examples/collect_stats/template_test.py b/testing/example_scripts/perf_examples/collect_stats/template_test.py
index 064ade190a1..d9449485db6 100644
--- a/testing/example_scripts/perf_examples/collect_stats/template_test.py
+++ b/testing/example_scripts/perf_examples/collect_stats/template_test.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_x():
     pass
diff --git a/testing/example_scripts/tmpdir/tmp_path_fixture.py b/testing/example_scripts/tmpdir/tmp_path_fixture.py
index 8675eb2fa62..503ead473e7 100644
--- a/testing/example_scripts/tmpdir/tmp_path_fixture.py
+++ b/testing/example_scripts/tmpdir/tmp_path_fixture.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py b/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py
index d421ce927c9..733202915e4 100644
--- a/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py
+++ b/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import unittest
 
 import pytest
diff --git a/testing/example_scripts/unittest/test_setup_skip.py b/testing/example_scripts/unittest/test_setup_skip.py
index 93f79bb3b2e..52ff96ea8be 100644
--- a/testing/example_scripts/unittest/test_setup_skip.py
+++ b/testing/example_scripts/unittest/test_setup_skip.py
@@ -1,4 +1,8 @@
+# mypy: allow-untyped-defs
 """Skipping an entire subclass with unittest.skip() should *not* call setUp from a base class."""
+
+from __future__ import annotations
+
 import unittest
 
 
diff --git a/testing/example_scripts/unittest/test_setup_skip_class.py b/testing/example_scripts/unittest/test_setup_skip_class.py
index 4f251dcba17..fe431d8e794 100644
--- a/testing/example_scripts/unittest/test_setup_skip_class.py
+++ b/testing/example_scripts/unittest/test_setup_skip_class.py
@@ -1,4 +1,8 @@
+# mypy: allow-untyped-defs
 """Skipping an entire subclass with unittest.skip() should *not* call setUpClass from a base class."""
+
+from __future__ import annotations
+
 import unittest
 
 
diff --git a/testing/example_scripts/unittest/test_setup_skip_module.py b/testing/example_scripts/unittest/test_setup_skip_module.py
index 98befbe510f..07fd96c9cef 100644
--- a/testing/example_scripts/unittest/test_setup_skip_module.py
+++ b/testing/example_scripts/unittest/test_setup_skip_module.py
@@ -1,4 +1,8 @@
+# mypy: allow-untyped-defs
 """setUpModule is always called, even if all tests in the module are skipped"""
+
+from __future__ import annotations
+
 import unittest
 
 
diff --git a/testing/example_scripts/unittest/test_unittest_asyncio.py b/testing/example_scripts/unittest/test_unittest_asyncio.py
index 1cd2168604c..8792492b38d 100644
--- a/testing/example_scripts/unittest/test_unittest_asyncio.py
+++ b/testing/example_scripts/unittest/test_unittest_asyncio.py
@@ -1,8 +1,10 @@
-from typing import List
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from unittest import IsolatedAsyncioTestCase
 
 
-teardowns: List[None] = []
+teardowns: list[None] = []
 
 
 class AsyncArguments(IsolatedAsyncioTestCase):
diff --git a/testing/example_scripts/unittest/test_unittest_asynctest.py b/testing/example_scripts/unittest/test_unittest_asynctest.py
index fb26617067c..8a93366b9a3 100644
--- a/testing/example_scripts/unittest/test_unittest_asynctest.py
+++ b/testing/example_scripts/unittest/test_unittest_asynctest.py
@@ -1,11 +1,14 @@
+# mypy: allow-untyped-defs
 """Issue #7110"""
+
+from __future__ import annotations
+
 import asyncio
-from typing import List
 
 import asynctest
 
 
-teardowns: List[None] = []
+teardowns: list[None] = []
 
 
 class Test(asynctest.TestCase):
diff --git a/testing/example_scripts/unittest/test_unittest_plain_async.py b/testing/example_scripts/unittest/test_unittest_plain_async.py
index 78dfece684e..ea1ae371551 100644
--- a/testing/example_scripts/unittest/test_unittest_plain_async.py
+++ b/testing/example_scripts/unittest/test_unittest_plain_async.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import unittest
 
 
diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message.py b/testing/example_scripts/warnings/test_group_warnings_by_message.py
index 6985caa4407..ee3bc2bbee4 100644
--- a/testing/example_scripts/warnings/test_group_warnings_by_message.py
+++ b/testing/example_scripts/warnings/test_group_warnings_by_message.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import warnings
 
 import pytest
diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py
index b8c11cb71c9..cc514bafbe9 100644
--- a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py
+++ b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import warnings
 
 import pytest
diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py
index 636d04a5505..33d5ce8ce34 100644
--- a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py
+++ b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from test_1 import func
 
 
diff --git a/testing/examples/test_issue519.py b/testing/examples/test_issue519.py
index 7b9c109889e..80f78d843a2 100644
--- a/testing/examples/test_issue519.py
+++ b/testing/examples/test_issue519.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from _pytest.pytester import Pytester
 
 
diff --git a/testing/freeze/create_executable.py b/testing/freeze/create_executable.py
index 998df7b1ca7..2015d22c7c0 100644
--- a/testing/freeze/create_executable.py
+++ b/testing/freeze/create_executable.py
@@ -1,11 +1,16 @@
 """Generate an executable with pytest runner embedded using PyInstaller."""
+
+from __future__ import annotations
+
+
 if __name__ == "__main__":
-    import pytest
     import subprocess
 
+    import pytest
+
     hidden = []
     for x in pytest.freeze_includes():
         hidden.extend(["--hidden-import", x])
     hidden.extend(["--hidden-import", "distutils"])
-    args = ["pyinstaller", "--noconfirm"] + hidden + ["runtests_script.py"]
+    args = ["pyinstaller", "--noconfirm", *hidden, "runtests_script.py"]
     subprocess.check_call(" ".join(args), shell=True)
diff --git a/testing/freeze/runtests_script.py b/testing/freeze/runtests_script.py
index 591863016ac..286c98ac539 100644
--- a/testing/freeze/runtests_script.py
+++ b/testing/freeze/runtests_script.py
@@ -3,8 +3,12 @@
 pytest main().
 """
 
+from __future__ import annotations
+
+
 if __name__ == "__main__":
     import sys
+
     import pytest
 
     sys.exit(pytest.main())
diff --git a/testing/freeze/tests/test_trivial.py b/testing/freeze/tests/test_trivial.py
index 08a55552abb..000ca97310c 100644
--- a/testing/freeze/tests/test_trivial.py
+++ b/testing/freeze/tests/test_trivial.py
@@ -1,3 +1,7 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_upper():
     assert "foo".upper() == "FOO"
 
diff --git a/testing/freeze/tox_run.py b/testing/freeze/tox_run.py
index 678a69c858a..38c1e75cf10 100644
--- a/testing/freeze/tox_run.py
+++ b/testing/freeze/tox_run.py
@@ -2,6 +2,10 @@
 Called by tox.ini: uses the generated executable to run the tests in ./tests/
 directory.
 """
+
+from __future__ import annotations
+
+
 if __name__ == "__main__":
     import os
     import sys
@@ -9,4 +13,4 @@
     executable = os.path.join(os.getcwd(), "dist", "runtests_script", "runtests_script")
     if sys.platform.startswith("win"):
         executable += ".exe"
-    sys.exit(os.system("%s tests" % executable))
+    sys.exit(os.system(f"{executable} tests"))
diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py
new file mode 100644
index 00000000000..1326ef34b2e
--- /dev/null
+++ b/testing/io/test_pprint.py
@@ -0,0 +1,408 @@
+from __future__ import annotations
+
+from collections import ChainMap
+from collections import Counter
+from collections import defaultdict
+from collections import deque
+from collections import OrderedDict
+from dataclasses import dataclass
+import textwrap
+from types import MappingProxyType
+from types import SimpleNamespace
+from typing import Any
+
+from _pytest._io.pprint import PrettyPrinter
+import pytest
+
+
+@dataclass
+class EmptyDataclass:
+    pass
+
+
+@dataclass
+class DataclassWithOneItem:
+    foo: str
+
+
+@dataclass
+class DataclassWithTwoItems:
+    foo: str
+    bar: str
+
+
+@pytest.mark.parametrize(
+    ("data", "expected"),
+    (
+        pytest.param(
+            EmptyDataclass(),
+            "EmptyDataclass()",
+            id="dataclass-empty",
+        ),
+        pytest.param(
+            DataclassWithOneItem(foo="bar"),
+            """
+            DataclassWithOneItem(
+                foo='bar',
+            )
+            """,
+            id="dataclass-one-item",
+        ),
+        pytest.param(
+            DataclassWithTwoItems(foo="foo", bar="bar"),
+            """
+            DataclassWithTwoItems(
+                foo='foo',
+                bar='bar',
+            )
+            """,
+            id="dataclass-two-items",
+        ),
+        pytest.param(
+            {},
+            "{}",
+            id="dict-empty",
+        ),
+        pytest.param(
+            {"one": 1},
+            """
+            {
+                'one': 1,
+            }
+            """,
+            id="dict-one-item",
+        ),
+        pytest.param(
+            {"one": 1, "two": 2},
+            """
+            {
+                'one': 1,
+                'two': 2,
+            }
+            """,
+            id="dict-two-items",
+        ),
+        pytest.param(OrderedDict(), "OrderedDict()", id="ordereddict-empty"),
+        pytest.param(
+            OrderedDict({"one": 1}),
+            """
+            OrderedDict({
+                'one': 1,
+            })
+            """,
+            id="ordereddict-one-item",
+        ),
+        pytest.param(
+            OrderedDict({"one": 1, "two": 2}),
+            """
+            OrderedDict({
+                'one': 1,
+                'two': 2,
+            })
+            """,
+            id="ordereddict-two-items",
+        ),
+        pytest.param(
+            [],
+            "[]",
+            id="list-empty",
+        ),
+        pytest.param(
+            [1],
+            """
+            [
+                1,
+            ]
+            """,
+            id="list-one-item",
+        ),
+        pytest.param(
+            [1, 2],
+            """
+            [
+                1,
+                2,
+            ]
+            """,
+            id="list-two-items",
+        ),
+        pytest.param(
+            tuple(),
+            "()",
+            id="tuple-empty",
+        ),
+        pytest.param(
+            (1,),
+            """
+            (
+                1,
+            )
+            """,
+            id="tuple-one-item",
+        ),
+        pytest.param(
+            (1, 2),
+            """
+            (
+                1,
+                2,
+            )
+            """,
+            id="tuple-two-items",
+        ),
+        pytest.param(
+            set(),
+            "set()",
+            id="set-empty",
+        ),
+        pytest.param(
+            {1},
+            """
+            {
+                1,
+            }
+            """,
+            id="set-one-item",
+        ),
+        pytest.param(
+            {1, 2},
+            """
+            {
+                1,
+                2,
+            }
+            """,
+            id="set-two-items",
+        ),
+        pytest.param(
+            MappingProxyType({}),
+            "mappingproxy({})",
+            id="mappingproxy-empty",
+        ),
+        pytest.param(
+            MappingProxyType({"one": 1}),
+            """
+            mappingproxy({
+                'one': 1,
+            })
+            """,
+            id="mappingproxy-one-item",
+        ),
+        pytest.param(
+            MappingProxyType({"one": 1, "two": 2}),
+            """
+            mappingproxy({
+                'one': 1,
+                'two': 2,
+            })
+            """,
+            id="mappingproxy-two-items",
+        ),
+        pytest.param(
+            SimpleNamespace(),
+            "namespace()",
+            id="simplenamespace-empty",
+        ),
+        pytest.param(
+            SimpleNamespace(one=1),
+            """
+            namespace(
+                one=1,
+            )
+            """,
+            id="simplenamespace-one-item",
+        ),
+        pytest.param(
+            SimpleNamespace(one=1, two=2),
+            """
+            namespace(
+                one=1,
+                two=2,
+            )
+            """,
+            id="simplenamespace-two-items",
+        ),
+        pytest.param(
+            defaultdict(str), "defaultdict(<class 'str'>, {})", id="defaultdict-empty"
+        ),
+        pytest.param(
+            defaultdict(str, {"one": "1"}),
+            """
+            defaultdict(<class 'str'>, {
+                'one': '1',
+            })
+            """,
+            id="defaultdict-one-item",
+        ),
+        pytest.param(
+            defaultdict(str, {"one": "1", "two": "2"}),
+            """
+            defaultdict(<class 'str'>, {
+                'one': '1',
+                'two': '2',
+            })
+            """,
+            id="defaultdict-two-items",
+        ),
+        pytest.param(
+            Counter(),
+            "Counter()",
+            id="counter-empty",
+        ),
+        pytest.param(
+            Counter("1"),
+            """
+            Counter({
+                '1': 1,
+            })
+            """,
+            id="counter-one-item",
+        ),
+        pytest.param(
+            Counter("121"),
+            """
+            Counter({
+                '1': 2,
+                '2': 1,
+            })
+            """,
+            id="counter-two-items",
+        ),
+        pytest.param(ChainMap(), "ChainMap({})", id="chainmap-empty"),
+        pytest.param(
+            ChainMap({"one": 1, "two": 2}),
+            """
+            ChainMap(
+                {
+                    'one': 1,
+                    'two': 2,
+                },
+            )
+            """,
+            id="chainmap-one-item",
+        ),
+        pytest.param(
+            ChainMap({"one": 1}, {"two": 2}),
+            """
+            ChainMap(
+                {
+                    'one': 1,
+                },
+                {
+                    'two': 2,
+                },
+            )
+            """,
+            id="chainmap-two-items",
+        ),
+        pytest.param(
+            deque(),
+            "deque([])",
+            id="deque-empty",
+        ),
+        pytest.param(
+            deque([1]),
+            """
+            deque([
+                1,
+            ])
+            """,
+            id="deque-one-item",
+        ),
+        pytest.param(
+            deque([1, 2]),
+            """
+            deque([
+                1,
+                2,
+            ])
+            """,
+            id="deque-two-items",
+        ),
+        pytest.param(
+            deque([1, 2], maxlen=3),
+            """
+            deque(maxlen=3, [
+                1,
+                2,
+            ])
+            """,
+            id="deque-maxlen",
+        ),
+        pytest.param(
+            {
+                "chainmap": ChainMap({"one": 1}, {"two": 2}),
+                "counter": Counter("122"),
+                "dataclass": DataclassWithTwoItems(foo="foo", bar="bar"),
+                "defaultdict": defaultdict(str, {"one": "1", "two": "2"}),
+                "deque": deque([1, 2], maxlen=3),
+                "dict": {"one": 1, "two": 2},
+                "list": [1, 2],
+                "mappingproxy": MappingProxyType({"one": 1, "two": 2}),
+                "ordereddict": OrderedDict({"one": 1, "two": 2}),
+                "set": {1, 2},
+                "simplenamespace": SimpleNamespace(one=1, two=2),
+                "tuple": (1, 2),
+            },
+            """
+            {
+                'chainmap': ChainMap(
+                    {
+                        'one': 1,
+                    },
+                    {
+                        'two': 2,
+                    },
+                ),
+                'counter': Counter({
+                    '2': 2,
+                    '1': 1,
+                }),
+                'dataclass': DataclassWithTwoItems(
+                    foo='foo',
+                    bar='bar',
+                ),
+                'defaultdict': defaultdict(<class 'str'>, {
+                    'one': '1',
+                    'two': '2',
+                }),
+                'deque': deque(maxlen=3, [
+                    1,
+                    2,
+                ]),
+                'dict': {
+                    'one': 1,
+                    'two': 2,
+                },
+                'list': [
+                    1,
+                    2,
+                ],
+                'mappingproxy': mappingproxy({
+                    'one': 1,
+                    'two': 2,
+                }),
+                'ordereddict': OrderedDict({
+                    'one': 1,
+                    'two': 2,
+                }),
+                'set': {
+                    1,
+                    2,
+                },
+                'simplenamespace': namespace(
+                    one=1,
+                    two=2,
+                ),
+                'tuple': (
+                    1,
+                    2,
+                ),
+            }
+            """,
+            id="deep-example",
+        ),
+    ),
+)
+def test_consistent_pretty_printer(data: Any, expected: str) -> None:
+    assert PrettyPrinter().pformat(data) == textwrap.dedent(expected).strip()
diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py
index 63d3af822b1..075d40cdf44 100644
--- a/testing/io/test_saferepr.py
+++ b/testing/io/test_saferepr.py
@@ -1,7 +1,10 @@
-import pytest
-from _pytest._io.saferepr import _pformat_dispatch
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
 from _pytest._io.saferepr import saferepr
+from _pytest._io.saferepr import saferepr_unlimited
+import pytest
 
 
 def test_simple_repr():
@@ -58,9 +61,7 @@ class BrokenReprException(Exception):
     obj = BrokenRepr(BrokenReprException("omg even worse"))
     s2 = saferepr(obj)
     assert s2 == (
-        "<[unpresentable exception ({!s}) raised in repr()] BrokenRepr object at 0x{:x}>".format(
-            exp_exc, id(obj)
-        )
+        f"<[unpresentable exception ({exp_exc!s}) raised in repr()] BrokenRepr object at 0x{id(obj):x}>"
     )
 
 
@@ -80,7 +81,7 @@ def raise_exc(self, *args):
                 raise self.exc_type(*args)
             raise self.exc_type
 
-        def __str__(self):
+        def __str__(self):  # noqa: PLE0307
             self.raise_exc("__str__")
 
         def __repr__(self):
@@ -98,14 +99,12 @@ def __repr__(self):
     baseexc_str = BaseException("__str__")
     obj = BrokenObj(RaisingOnStrRepr([BaseException]))
     assert saferepr(obj) == (
-        "<[unpresentable exception ({!r}) "
-        "raised in repr()] BrokenObj object at 0x{:x}>".format(baseexc_str, id(obj))
+        f"<[unpresentable exception ({baseexc_str!r}) "
+        f"raised in repr()] BrokenObj object at 0x{id(obj):x}>"
     )
     obj = BrokenObj(RaisingOnStrRepr([RaisingOnStrRepr([BaseException])]))
     assert saferepr(obj) == (
-        "<[{!r} raised in repr()] BrokenObj object at 0x{:x}>".format(
-            baseexc_str, id(obj)
-        )
+        f"<[{baseexc_str!r} raised in repr()] BrokenObj object at 0x{id(obj):x}>"
     )
 
     with pytest.raises(KeyboardInterrupt):
@@ -147,7 +146,7 @@ def test_big_repr():
 def test_repr_on_newstyle() -> None:
     class Function:
         def __repr__(self):
-            return "<%s>" % (self.name)  # type: ignore[attr-defined]
+            return f"<{self.name}>"  # type: ignore[attr-defined]
 
     assert saferepr(Function())
 
@@ -158,12 +157,6 @@ def test_unicode():
     assert saferepr(val) == reprval
 
 
-def test_pformat_dispatch():
-    assert _pformat_dispatch("a") == "'a'"
-    assert _pformat_dispatch("a" * 10, width=5) == "'aaaaaaaaaa'"
-    assert _pformat_dispatch("foo bar", width=5) == "('foo '\n 'bar')"
-
-
 def test_broken_getattribute():
     """saferepr() can create proper representations of classes with
     broken __getattribute__ (#7145)
@@ -179,3 +172,23 @@ def __repr__(self):
     assert saferepr(SomeClass()).startswith(
         "<[RuntimeError() raised in repr()] SomeClass object at 0x"
     )
+
+
+def test_saferepr_unlimited():
+    dict5 = {f"v{i}": i for i in range(5)}
+    assert saferepr_unlimited(dict5) == "{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4}"
+
+    dict_long = {f"v{i}": i for i in range(1_000)}
+    r = saferepr_unlimited(dict_long)
+    assert "..." not in r
+    assert "\n" not in r
+
+
+def test_saferepr_unlimited_exc():
+    class A:
+        def __repr__(self):
+            raise ValueError(42)
+
+    assert saferepr_unlimited(A()).startswith(
+        "<[ValueError(42) raised in repr()] A object at 0x"
+    )
diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py
index 4866c94a558..1f38d6f15d9 100644
--- a/testing/io/test_terminalwriter.py
+++ b/testing/io/test_terminalwriter.py
@@ -1,15 +1,19 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Generator
 import io
+from io import StringIO
 import os
+from pathlib import Path
 import re
 import shutil
 import sys
-from pathlib import Path
-from typing import Generator
 from unittest import mock
 
-import pytest
 from _pytest._io import terminalwriter
 from _pytest.monkeypatch import MonkeyPatch
+import pytest
 
 
 # These tests were initially copied from py 1.8.1.
@@ -56,7 +60,7 @@ def test_terminalwriter_not_unicode() -> None:
     file = io.TextIOWrapper(buffer, encoding="cp1252")
     tw = terminalwriter.TerminalWriter(file)
     tw.write("hello 🌀 wôrld אבג", flush=True)
-    assert buffer.getvalue() == br"hello \U0001f300 w\xf4rld \u05d0\u05d1\u05d2"
+    assert buffer.getvalue() == rb"hello \U0001f300 w\xf4rld \u05d0\u05d1\u05d2"
 
 
 win32 = int(sys.platform == "win32")
@@ -64,9 +68,8 @@ def test_terminalwriter_not_unicode() -> None:
 
 class TestTerminalWriter:
     @pytest.fixture(params=["path", "stringio"])
-    def tw(
-        self, request, tmp_path: Path
-    ) -> Generator[terminalwriter.TerminalWriter, None, None]:
+    def tw(self, request, tmp_path: Path) -> Generator[terminalwriter.TerminalWriter]:
+        f: io.TextIOWrapper | StringIO
         if request.param == "path":
             p = tmp_path.joinpath("tmpfile")
             f = open(str(p), "w+", encoding="utf8")
@@ -164,53 +167,67 @@ def test_attr_hasmarkup() -> None:
     assert "\x1b[0m" in s
 
 
-def assert_color_set():
+def assert_color(expected: bool, default: bool | None = None) -> None:
     file = io.StringIO()
-    tw = terminalwriter.TerminalWriter(file)
-    assert tw.hasmarkup
+    if default is None:
+        default = not expected
+    file.isatty = lambda: default  # type: ignore
+    tw = terminalwriter.TerminalWriter(file=file)
+    assert tw.hasmarkup is expected
     tw.line("hello", bold=True)
     s = file.getvalue()
-    assert len(s) > len("hello\n")
-    assert "\x1b[1m" in s
-    assert "\x1b[0m" in s
-
-
-def assert_color_not_set():
-    f = io.StringIO()
-    f.isatty = lambda: True  # type: ignore
-    tw = terminalwriter.TerminalWriter(file=f)
-    assert not tw.hasmarkup
-    tw.line("hello", bold=True)
-    s = f.getvalue()
-    assert s == "hello\n"
+    if expected:
+        assert len(s) > len("hello\n")
+        assert "\x1b[1m" in s
+        assert "\x1b[0m" in s
+    else:
+        assert s == "hello\n"
 
 
 def test_should_do_markup_PY_COLORS_eq_1(monkeypatch: MonkeyPatch) -> None:
     monkeypatch.setitem(os.environ, "PY_COLORS", "1")
-    assert_color_set()
+    assert_color(True)
 
 
 def test_should_not_do_markup_PY_COLORS_eq_0(monkeypatch: MonkeyPatch) -> None:
     monkeypatch.setitem(os.environ, "PY_COLORS", "0")
-    assert_color_not_set()
+    assert_color(False)
 
 
 def test_should_not_do_markup_NO_COLOR(monkeypatch: MonkeyPatch) -> None:
     monkeypatch.setitem(os.environ, "NO_COLOR", "1")
-    assert_color_not_set()
+    assert_color(False)
 
 
 def test_should_do_markup_FORCE_COLOR(monkeypatch: MonkeyPatch) -> None:
     monkeypatch.setitem(os.environ, "FORCE_COLOR", "1")
-    assert_color_set()
+    assert_color(True)
 
 
-def test_should_not_do_markup_NO_COLOR_and_FORCE_COLOR(
+@pytest.mark.parametrize(
+    ["NO_COLOR", "FORCE_COLOR", "expected"],
+    [
+        ("1", "1", False),
+        ("", "1", True),
+        ("1", "", False),
+    ],
+)
+def test_NO_COLOR_and_FORCE_COLOR(
     monkeypatch: MonkeyPatch,
+    NO_COLOR: str,
+    FORCE_COLOR: str,
+    expected: bool,
 ) -> None:
-    monkeypatch.setitem(os.environ, "NO_COLOR", "1")
-    monkeypatch.setitem(os.environ, "FORCE_COLOR", "1")
-    assert_color_not_set()
+    monkeypatch.setitem(os.environ, "NO_COLOR", NO_COLOR)
+    monkeypatch.setitem(os.environ, "FORCE_COLOR", FORCE_COLOR)
+    assert_color(expected)
+
+
+def test_empty_NO_COLOR_and_FORCE_COLOR_ignored(monkeypatch: MonkeyPatch) -> None:
+    monkeypatch.setitem(os.environ, "NO_COLOR", "")
+    monkeypatch.setitem(os.environ, "FORCE_COLOR", "")
+    assert_color(True, True)
+    assert_color(False, False)
 
 
 class TestTerminalWriterLineWidth:
@@ -254,7 +271,7 @@ def test_combining(self) -> None:
         pytest.param(
             True,
             True,
-            "{kw}assert{hl-reset} {number}0{hl-reset}\n",
+            "{reset}{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n",
             id="with markup and code_highlight",
         ),
         pytest.param(
@@ -291,3 +308,17 @@ def test_code_highlight(has_markup, code_highlight, expected, color_mapping):
         match=re.escape("indents size (2) should have same size as lines (1)"),
     ):
         tw._write_source(["assert 0"], [" ", " "])
+
+
+def test_highlight_empty_source() -> None:
+    """Don't crash trying to highlight empty source code.
+
+    Issue #11758.
+    """
+    f = io.StringIO()
+    tw = terminalwriter.TerminalWriter(f)
+    tw.hasmarkup = True
+    tw.code_highlight = True
+    tw._write_source([])
+
+    assert f.getvalue() == ""
diff --git a/testing/io/test_wcwidth.py b/testing/io/test_wcwidth.py
index 7cc74df5d07..9ff1ad06e60 100644
--- a/testing/io/test_wcwidth.py
+++ b/testing/io/test_wcwidth.py
@@ -1,6 +1,8 @@
-import pytest
+from __future__ import annotations
+
 from _pytest._io.wcwidth import wcswidth
 from _pytest._io.wcwidth import wcwidth
+import pytest
 
 
 @pytest.mark.parametrize(
@@ -11,11 +13,11 @@
         ("a", 1),
         ("1", 1),
         ("א", 1),
-        ("\u200B", 0),
-        ("\u1ABE", 0),
+        ("\u200b", 0),
+        ("\u1abe", 0),
         ("\u0591", 0),
         ("🉐", 2),
-        ("$", 2),
+        ("$", 2),  # noqa: RUF001
     ],
 )
 def test_wcwidth(c: str, expected: int) -> None:
diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py
index bcb20de5805..5f94cb8508a 100644
--- a/testing/logging/test_fixture.py
+++ b/testing/logging/test_fixture.py
@@ -1,19 +1,38 @@
+# mypy: disable-error-code="attr-defined"
+# mypy: disallow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Iterator
 import logging
 
-import pytest
 from _pytest.logging import caplog_records_key
 from _pytest.pytester import Pytester
+import pytest
+
 
 logger = logging.getLogger(__name__)
 sublogger = logging.getLogger(__name__ + ".baz")
 
 
+@pytest.fixture(autouse=True)
+def cleanup_disabled_logging() -> Iterator[None]:
+    """Simple fixture that ensures that a test doesn't disable logging.
+
+    This is necessary because ``logging.disable()`` is global, so a test disabling logging
+    and not cleaning up after will break every test that runs after it.
+
+    This behavior was moved to a fixture so that logging will be un-disabled even if the test fails an assertion.
+    """
+    yield
+    logging.disable(logging.NOTSET)
+
+
 def test_fixture_help(pytester: Pytester) -> None:
     result = pytester.runpytest("--fixtures")
     result.stdout.fnmatch_lines(["*caplog*"])
 
 
-def test_change_level(caplog):
+def test_change_level(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.debug("handler DEBUG level")
     logger.info("handler INFO level")
@@ -28,10 +47,27 @@ def test_change_level(caplog):
     assert "CRITICAL" in caplog.text
 
 
+def test_change_level_logging_disabled(caplog: pytest.LogCaptureFixture) -> None:
+    logging.disable(logging.CRITICAL)
+    assert logging.root.manager.disable == logging.CRITICAL
+    caplog.set_level(logging.WARNING)
+    logger.info("handler INFO level")
+    logger.warning("handler WARNING level")
+
+    caplog.set_level(logging.CRITICAL, logger=sublogger.name)
+    sublogger.warning("logger SUB_WARNING level")
+    sublogger.critical("logger SUB_CRITICAL level")
+
+    assert "INFO" not in caplog.text
+    assert "WARNING" in caplog.text
+    assert "SUB_WARNING" not in caplog.text
+    assert "SUB_CRITICAL" in caplog.text
+
+
 def test_change_level_undo(pytester: Pytester) -> None:
     """Ensure that 'set_level' is undone after the end of the test.
 
-    Tests the logging output themselves (affacted both by logger and handler levels).
+    Tests the logging output themselves (affected both by logger and handler levels).
     """
     pytester.makepyfile(
         """
@@ -54,7 +90,36 @@ def test2(caplog):
     result.stdout.no_fnmatch_line("*log from test2*")
 
 
-def test_change_level_undos_handler_level(pytester: Pytester) -> None:
+def test_change_disabled_level_undo(pytester: Pytester) -> None:
+    """Ensure that '_force_enable_logging' in 'set_level' is undone after the end of the test.
+
+    Tests the logging output themselves (affected by disabled logging level).
+    """
+    pytester.makepyfile(
+        """
+        import logging
+
+        def test1(caplog):
+            logging.disable(logging.CRITICAL)
+            caplog.set_level(logging.INFO)
+            # using + operator here so fnmatch_lines doesn't match the code in the traceback
+            logging.info('log from ' + 'test1')
+            assert 0
+
+        def test2(caplog):
+            # using + operator here so fnmatch_lines doesn't match the code in the traceback
+            # use logging.warning because we need a level that will show up if logging.disabled
+            # isn't reset to ``CRITICAL`` after test1.
+            logging.warning('log from ' + 'test2')
+            assert 0
+    """
+    )
+    result = pytester.runpytest()
+    result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"])
+    result.stdout.no_fnmatch_line("*log from test2*")
+
+
+def test_change_level_undoes_handler_level(pytester: Pytester) -> None:
     """Ensure that 'set_level' is undone after the end of the test (handler).
 
     Issue #7569. Tests the handler level specifically.
@@ -82,7 +147,7 @@ def test3(caplog):
     result.assert_outcomes(passed=3)
 
 
-def test_with_statement(caplog):
+def test_with_statement_at_level(caplog: pytest.LogCaptureFixture) -> None:
     with caplog.at_level(logging.INFO):
         logger.debug("handler DEBUG level")
         logger.info("handler INFO level")
@@ -97,7 +162,84 @@ def test_with_statement(caplog):
     assert "CRITICAL" in caplog.text
 
 
-def test_log_access(caplog):
+def test_with_statement_at_level_logging_disabled(
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    logging.disable(logging.CRITICAL)
+    assert logging.root.manager.disable == logging.CRITICAL
+    with caplog.at_level(logging.WARNING):
+        logger.debug("handler DEBUG level")
+        logger.info("handler INFO level")
+        logger.warning("handler WARNING level")
+        logger.error("handler ERROR level")
+        logger.critical("handler CRITICAL level")
+
+        assert logging.root.manager.disable == logging.INFO
+
+        with caplog.at_level(logging.CRITICAL, logger=sublogger.name):
+            sublogger.warning("logger SUB_WARNING level")
+            sublogger.critical("logger SUB_CRITICAL level")
+
+    assert "DEBUG" not in caplog.text
+    assert "INFO" not in caplog.text
+    assert "WARNING" in caplog.text
+    assert "ERROR" in caplog.text
+    assert " CRITICAL" in caplog.text
+    assert "SUB_WARNING" not in caplog.text
+    assert "SUB_CRITICAL" in caplog.text
+    assert logging.root.manager.disable == logging.CRITICAL
+
+
+def test_with_statement_filtering(caplog: pytest.LogCaptureFixture) -> None:
+    class TestFilter(logging.Filter):
+        def filter(self, record: logging.LogRecord) -> bool:
+            record.msg = "filtered handler call"
+            return True
+
+    with caplog.at_level(logging.INFO):
+        with caplog.filtering(TestFilter()):
+            logger.info("handler call")
+        logger.info("handler call")
+
+    filtered_tuple, unfiltered_tuple = caplog.record_tuples
+    assert filtered_tuple == ("test_fixture", 20, "filtered handler call")
+    assert unfiltered_tuple == ("test_fixture", 20, "handler call")
+
+
+@pytest.mark.parametrize(
+    "level_str,expected_disable_level",
+    [
+        ("CRITICAL", logging.ERROR),
+        ("ERROR", logging.WARNING),
+        ("WARNING", logging.INFO),
+        ("INFO", logging.DEBUG),
+        ("DEBUG", logging.NOTSET),
+        ("NOTSET", logging.NOTSET),
+        ("NOTVALIDLEVEL", logging.NOTSET),
+    ],
+)
+def test_force_enable_logging_level_string(
+    caplog: pytest.LogCaptureFixture, level_str: str, expected_disable_level: int
+) -> None:
+    """Test _force_enable_logging using a level string.
+
+    ``expected_disable_level`` is one level below ``level_str`` because the disabled log level
+    always needs to be *at least* one level lower than the level that caplog is trying to capture.
+    """
+    test_logger = logging.getLogger("test_str_level_force_enable")
+    # Emulate a testing environment where all logging is disabled.
+    logging.disable(logging.CRITICAL)
+    # Make sure all logging is disabled.
+    assert not test_logger.isEnabledFor(logging.CRITICAL)
+    # Un-disable logging for `level_str`.
+    caplog._force_enable_logging(level_str, test_logger)
+    # Make sure that the disabled level is now one below the requested logging level.
+    # We don't use `isEnabledFor` here because that also checks the level set by
+    # `logging.setLevel()` which is irrelevant to `logging.disable()`.
+    assert test_logger.manager.disable == expected_disable_level
+
+
+def test_log_access(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("boo %s", "arg")
     assert caplog.records[0].levelname == "INFO"
@@ -105,7 +247,7 @@ def test_log_access(caplog):
     assert "boo arg" in caplog.text
 
 
-def test_messages(caplog):
+def test_messages(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("boo %s", "arg")
     logger.info("bar %s\nbaz %s", "arg1", "arg2")
@@ -126,14 +268,14 @@ def test_messages(caplog):
     assert "Exception" not in caplog.messages[-1]
 
 
-def test_record_tuples(caplog):
+def test_record_tuples(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("boo %s", "arg")
 
     assert caplog.record_tuples == [(__name__, logging.INFO, "boo arg")]
 
 
-def test_unicode(caplog):
+def test_unicode(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("bū")
     assert caplog.records[0].levelname == "INFO"
@@ -141,7 +283,7 @@ def test_unicode(caplog):
     assert "bū" in caplog.text
 
 
-def test_clear(caplog):
+def test_clear(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("bū")
     assert len(caplog.records)
@@ -152,7 +294,9 @@ def test_clear(caplog):
 
 
 @pytest.fixture
-def logging_during_setup_and_teardown(caplog):
+def logging_during_setup_and_teardown(
+    caplog: pytest.LogCaptureFixture,
+) -> Iterator[None]:
     caplog.set_level("INFO")
     logger.info("a_setup_log")
     yield
@@ -160,7 +304,17 @@ def logging_during_setup_and_teardown(caplog):
     assert [x.message for x in caplog.get_records("teardown")] == ["a_teardown_log"]
 
 
-def test_caplog_captures_for_all_stages(caplog, logging_during_setup_and_teardown):
+def private_assert_caplog_records_is_setup_call(
+    caplog: pytest.LogCaptureFixture,
+) -> None:
+    # This reaches into private API, don't use this type of thing in real tests!
+    caplog_records = caplog._item.stash[caplog_records_key]
+    assert set(caplog_records) == {"setup", "call"}
+
+
+def test_captures_for_all_stages(
+    caplog: pytest.LogCaptureFixture, logging_during_setup_and_teardown: None
+) -> None:
     assert not caplog.records
     assert not caplog.get_records("call")
     logger.info("a_call_log")
@@ -168,8 +322,27 @@ def test_caplog_captures_for_all_stages(caplog, logging_during_setup_and_teardow
 
     assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"]
 
-    # This reaches into private API, don't use this type of thing in real tests!
-    assert set(caplog._item.stash[caplog_records_key]) == {"setup", "call"}
+    private_assert_caplog_records_is_setup_call(caplog)
+
+
+def test_clear_for_call_stage(
+    caplog: pytest.LogCaptureFixture, logging_during_setup_and_teardown: None
+) -> None:
+    logger.info("a_call_log")
+    assert [x.message for x in caplog.get_records("call")] == ["a_call_log"]
+    assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"]
+    private_assert_caplog_records_is_setup_call(caplog)
+
+    caplog.clear()
+
+    assert caplog.get_records("call") == []
+    assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"]
+    private_assert_caplog_records_is_setup_call(caplog)
+
+    logging.info("a_call_log_after_clear")
+    assert [x.message for x in caplog.get_records("call")] == ["a_call_log_after_clear"]
+    assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"]
+    private_assert_caplog_records_is_setup_call(caplog)
 
 
 def test_ini_controls_global_log_level(pytester: Pytester) -> None:
@@ -195,11 +368,11 @@ def test_log_level_override(request, caplog):
     )
 
     result = pytester.runpytest()
-    # make sure that that we get a '0' exit code for the testsuite
+    # make sure that we get a '0' exit code for the testsuite
     assert result.ret == 0
 
 
-def test_caplog_can_override_global_log_level(pytester: Pytester) -> None:
+def test_can_override_global_log_level(pytester: Pytester) -> None:
     pytester.makepyfile(
         """
         import pytest
@@ -238,7 +411,7 @@ def test_log_level_override(request, caplog):
     assert result.ret == 0
 
 
-def test_caplog_captures_despite_exception(pytester: Pytester) -> None:
+def test_captures_despite_exception(pytester: Pytester) -> None:
     pytester.makepyfile(
         """
         import pytest
diff --git a/testing/logging/test_formatter.py b/testing/logging/test_formatter.py
index 37971293726..cfe3bee68c4 100644
--- a/testing/logging/test_formatter.py
+++ b/testing/logging/test_formatter.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 import logging
 from typing import Any
 
diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py
index 323ff7b2446..cf54788e246 100644
--- a/testing/logging/test_reporting.py
+++ b/testing/logging/test_reporting.py
@@ -1,14 +1,17 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import io
 import os
 import re
 from typing import cast
 
-import pytest
 from _pytest.capture import CaptureManager
 from _pytest.config import ExitCode
 from _pytest.fixtures import FixtureRequest
 from _pytest.pytester import Pytester
 from _pytest.terminal import TerminalReporter
+import pytest
 
 
 def test_nothing_logged(pytester: Pytester) -> None:
@@ -77,14 +80,14 @@ def test_foo():
     assert "warning text going to logger" not in stdout
     assert "info text going to logger" not in stdout
 
-    # The log file should contain the warning and the error log messages and
-    # not the info one, because the default level of the root logger is
-    # WARNING.
+    # The log file should only contain the error log messages and
+    # not the warning or info ones, because the root logger is set to
+    # ERROR using --log-level=ERROR.
     assert os.path.isfile(log_file)
-    with open(log_file) as rfh:
+    with open(log_file, encoding="utf-8") as rfh:
         contents = rfh.read()
         assert "info text going to logger" not in contents
-        assert "warning text going to logger" in contents
+        assert "warning text going to logger" not in contents
         assert "error text going to logger" in contents
 
 
@@ -176,13 +179,11 @@ def teardown_function(function):
 def test_log_cli_enabled_disabled(pytester: Pytester, enabled: bool) -> None:
     msg = "critical message logged by test"
     pytester.makepyfile(
-        """
+        f"""
         import logging
         def test_log_cli():
-            logging.critical("{}")
-    """.format(
-            msg
-        )
+            logging.critical("{msg}")
+    """
     )
     if enabled:
         pytester.makeini(
@@ -656,12 +657,79 @@ def test_log_file(request):
     # make sure that we get a '0' exit code for the testsuite
     assert result.ret == 0
     assert os.path.isfile(log_file)
-    with open(log_file) as rfh:
+    with open(log_file, encoding="utf-8") as rfh:
+        contents = rfh.read()
+        assert "This log message will be shown" in contents
+        assert "This log message won't be shown" not in contents
+
+
+def test_log_file_mode_cli(pytester: Pytester) -> None:
+    # Default log file level
+    pytester.makepyfile(
+        """
+        import pytest
+        import logging
+        def test_log_file(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_file_handler.level == logging.WARNING
+            logging.getLogger('catchlog').info("This log message won't be shown")
+            logging.getLogger('catchlog').warning("This log message will be shown")
+            print('PASSED')
+    """
+    )
+
+    log_file = str(pytester.path.joinpath("pytest.log"))
+
+    with open(log_file, mode="w", encoding="utf-8") as wfh:
+        wfh.write("A custom header\n")
+
+    result = pytester.runpytest(
+        "-s",
+        f"--log-file={log_file}",
+        "--log-file-mode=a",
+        "--log-file-level=WARNING",
+    )
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines(["test_log_file_mode_cli.py PASSED"])
+
+    # make sure that we get a '0' exit code for the testsuite
+    assert result.ret == 0
+    assert os.path.isfile(log_file)
+    with open(log_file, encoding="utf-8") as rfh:
         contents = rfh.read()
+        assert "A custom header" in contents
         assert "This log message will be shown" in contents
         assert "This log message won't be shown" not in contents
 
 
+def test_log_file_mode_cli_invalid(pytester: Pytester) -> None:
+    # Default log file level
+    pytester.makepyfile(
+        """
+        import pytest
+        import logging
+        def test_log_file(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_file_handler.level == logging.WARNING
+            logging.getLogger('catchlog').info("This log message won't be shown")
+            logging.getLogger('catchlog').warning("This log message will be shown")
+    """
+    )
+
+    log_file = str(pytester.path.joinpath("pytest.log"))
+
+    result = pytester.runpytest(
+        "-s",
+        f"--log-file={log_file}",
+        "--log-file-mode=b",
+        "--log-file-level=WARNING",
+    )
+
+    # make sure that we get a '4' exit code for the testsuite
+    assert result.ret == ExitCode.USAGE_ERROR
+
+
 def test_log_file_cli_level(pytester: Pytester) -> None:
     # Default log file level
     pytester.makepyfile(
@@ -687,7 +755,7 @@ def test_log_file(request):
     # make sure that we get a '0' exit code for the testsuite
     assert result.ret == 0
     assert os.path.isfile(log_file)
-    with open(log_file) as rfh:
+    with open(log_file, encoding="utf-8") as rfh:
         contents = rfh.read()
         assert "This log message will be shown" in contents
         assert "This log message won't be shown" not in contents
@@ -709,13 +777,11 @@ def test_log_file_ini(pytester: Pytester) -> None:
     log_file = str(pytester.path.joinpath("pytest.log"))
 
     pytester.makeini(
-        """
+        f"""
         [pytest]
-        log_file={}
+        log_file={log_file}
         log_file_level=WARNING
-        """.format(
-            log_file
-        )
+        """
     )
     pytester.makepyfile(
         """
@@ -738,23 +804,62 @@ def test_log_file(request):
     # make sure that we get a '0' exit code for the testsuite
     assert result.ret == 0
     assert os.path.isfile(log_file)
-    with open(log_file) as rfh:
+    with open(log_file, encoding="utf-8") as rfh:
         contents = rfh.read()
         assert "This log message will be shown" in contents
         assert "This log message won't be shown" not in contents
 
 
-def test_log_file_ini_level(pytester: Pytester) -> None:
+def test_log_file_mode_ini(pytester: Pytester) -> None:
     log_file = str(pytester.path.joinpath("pytest.log"))
 
     pytester.makeini(
+        f"""
+        [pytest]
+        log_file={log_file}
+        log_file_mode=a
+        log_file_level=WARNING
         """
+    )
+    pytester.makepyfile(
+        """
+        import pytest
+        import logging
+        def test_log_file(request):
+            plugin = request.config.pluginmanager.getplugin('logging-plugin')
+            assert plugin.log_file_handler.level == logging.WARNING
+            logging.getLogger('catchlog').info("This log message won't be shown")
+            logging.getLogger('catchlog').warning("This log message will be shown")
+            print('PASSED')
+    """
+    )
+
+    with open(log_file, mode="w", encoding="utf-8") as wfh:
+        wfh.write("A custom header\n")
+
+    result = pytester.runpytest("-s")
+
+    # fnmatch_lines does an assertion internally
+    result.stdout.fnmatch_lines(["test_log_file_mode_ini.py PASSED"])
+
+    assert result.ret == ExitCode.OK
+    assert os.path.isfile(log_file)
+    with open(log_file, encoding="utf-8") as rfh:
+        contents = rfh.read()
+        assert "A custom header" in contents
+        assert "This log message will be shown" in contents
+        assert "This log message won't be shown" not in contents
+
+
+def test_log_file_ini_level(pytester: Pytester) -> None:
+    log_file = str(pytester.path.joinpath("pytest.log"))
+
+    pytester.makeini(
+        f"""
         [pytest]
-        log_file={}
+        log_file={log_file}
         log_file_level = INFO
-        """.format(
-            log_file
-        )
+        """
     )
     pytester.makepyfile(
         """
@@ -777,7 +882,7 @@ def test_log_file(request):
     # make sure that we get a '0' exit code for the testsuite
     assert result.ret == 0
     assert os.path.isfile(log_file)
-    with open(log_file) as rfh:
+    with open(log_file, encoding="utf-8") as rfh:
         contents = rfh.read()
         assert "This log message will be shown" in contents
         assert "This log message won't be shown" not in contents
@@ -787,13 +892,11 @@ def test_log_file_unicode(pytester: Pytester) -> None:
     log_file = str(pytester.path.joinpath("pytest.log"))
 
     pytester.makeini(
-        """
+        f"""
         [pytest]
-        log_file={}
+        log_file={log_file}
         log_file_level = INFO
-        """.format(
-            log_file
-        )
+        """
     )
     pytester.makepyfile(
         """\
@@ -830,9 +933,10 @@ def test_live_logging_suspends_capture(
     We parametrize the test to also make sure _LiveLoggingStreamHandler works correctly if no capture manager plugin
     is installed.
     """
-    import logging
     import contextlib
     from functools import partial
+    import logging
+
     from _pytest.logging import _LiveLoggingStreamHandler
 
     class MockCaptureManager:
@@ -922,13 +1026,11 @@ def test_collection_logging_to_file(pytester: Pytester) -> None:
     log_file = str(pytester.path.joinpath("pytest.log"))
 
     pytester.makeini(
-        """
+        f"""
         [pytest]
-        log_file={}
+        log_file={log_file}
         log_file_level = INFO
-        """.format(
-            log_file
-        )
+        """
     )
 
     pytester.makepyfile(
@@ -960,14 +1062,12 @@ def test_log_in_hooks(pytester: Pytester) -> None:
     log_file = str(pytester.path.joinpath("pytest.log"))
 
     pytester.makeini(
-        """
+        f"""
         [pytest]
-        log_file={}
+        log_file={log_file}
         log_file_level = INFO
         log_cli=true
-        """.format(
-            log_file
-        )
+        """
     )
     pytester.makeconftest(
         """
@@ -985,7 +1085,7 @@ def pytest_sessionfinish(session, exitstatus):
     )
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(["*sessionstart*", "*runtestloop*", "*sessionfinish*"])
-    with open(log_file) as rfh:
+    with open(log_file, encoding="utf-8") as rfh:
         contents = rfh.read()
         assert "sessionstart" in contents
         assert "runtestloop" in contents
@@ -996,14 +1096,12 @@ def test_log_in_runtest_logreport(pytester: Pytester) -> None:
     log_file = str(pytester.path.joinpath("pytest.log"))
 
     pytester.makeini(
-        """
+        f"""
         [pytest]
-        log_file={}
+        log_file={log_file}
         log_file_level = INFO
         log_cli=true
-        """.format(
-            log_file
-        )
+        """
     )
     pytester.makeconftest(
         """
@@ -1021,7 +1119,7 @@ def test_first():
         """
     )
     pytester.runpytest()
-    with open(log_file) as rfh:
+    with open(log_file, encoding="utf-8") as rfh:
         contents = rfh.read()
         assert contents.count("logreport") == 3
 
@@ -1037,19 +1135,17 @@ def test_log_set_path(pytester: Pytester) -> None:
         """
     )
     pytester.makeconftest(
-        """
+        f"""
             import os
             import pytest
-            @pytest.hookimpl(hookwrapper=True, tryfirst=True)
+            @pytest.hookimpl(wrapper=True, tryfirst=True)
             def pytest_runtest_setup(item):
                 config = item.config
                 logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
-                report_file = os.path.join({}, item._request.node.name)
+                report_file = os.path.join({report_dir_base!r}, item._request.node.name)
                 logging_plugin.set_log_path(report_file)
-                yield
-        """.format(
-            repr(report_dir_base)
-        )
+                return (yield)
+        """
     )
     pytester.makepyfile(
         """
@@ -1065,15 +1161,75 @@ def test_second():
         """
     )
     pytester.runpytest()
-    with open(os.path.join(report_dir_base, "test_first")) as rfh:
+    with open(os.path.join(report_dir_base, "test_first"), encoding="utf-8") as rfh:
         content = rfh.read()
         assert "message from test 1" in content
 
-    with open(os.path.join(report_dir_base, "test_second")) as rfh:
+    with open(os.path.join(report_dir_base, "test_second"), encoding="utf-8") as rfh:
         content = rfh.read()
         assert "message from test 2" in content
 
 
+def test_log_set_path_with_log_file_mode(pytester: Pytester) -> None:
+    report_dir_base = str(pytester.path)
+
+    pytester.makeini(
+        """
+        [pytest]
+        log_file_level = DEBUG
+        log_cli=true
+        log_file_mode=a
+        """
+    )
+    pytester.makeconftest(
+        f"""
+            import os
+            import pytest
+            @pytest.hookimpl(wrapper=True, tryfirst=True)
+            def pytest_runtest_setup(item):
+                config = item.config
+                logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
+                report_file = os.path.join({report_dir_base!r}, item._request.node.name)
+                logging_plugin.set_log_path(report_file)
+                return (yield)
+        """
+    )
+    pytester.makepyfile(
+        """
+            import logging
+            logger = logging.getLogger("testcase-logger")
+            def test_first():
+                logger.info("message from test 1")
+                assert True
+
+            def test_second():
+                logger.debug("message from test 2")
+                assert True
+        """
+    )
+
+    test_first_log_file = os.path.join(report_dir_base, "test_first")
+    test_second_log_file = os.path.join(report_dir_base, "test_second")
+    with open(test_first_log_file, mode="w", encoding="utf-8") as wfh:
+        wfh.write("A custom header for test 1\n")
+
+    with open(test_second_log_file, mode="w", encoding="utf-8") as wfh:
+        wfh.write("A custom header for test 2\n")
+
+    result = pytester.runpytest()
+    assert result.ret == ExitCode.OK
+
+    with open(test_first_log_file, encoding="utf-8") as rfh:
+        content = rfh.read()
+        assert "A custom header for test 1" in content
+        assert "message from test 1" in content
+
+    with open(test_second_log_file, encoding="utf-8") as rfh:
+        content = rfh.read()
+        assert "A custom header for test 2" in content
+        assert "message from test 2" in content
+
+
 def test_colored_captured_log(pytester: Pytester) -> None:
     """Test that the level names of captured log messages of a failing test
     are colored."""
@@ -1165,3 +1321,228 @@ def test_log_file_cli_subdirectories_are_successfully_created(
     result = pytester.runpytest("--log-file=foo/bar/logf.log")
     assert "logf.log" in os.listdir(expected)
     assert result.ret == ExitCode.OK
+
+
+def test_disable_loggers(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+        import logging
+        import os
+        disabled_log = logging.getLogger('disabled')
+        test_log = logging.getLogger('test')
+        def test_logger_propagation(caplog):
+            with caplog.at_level(logging.DEBUG):
+                disabled_log.warning("no log; no stderr")
+                test_log.debug("Visible text!")
+                assert caplog.record_tuples == [('test', 10, 'Visible text!')]
+         """
+    )
+    result = pytester.runpytest("--log-disable=disabled", "-s")
+    assert result.ret == ExitCode.OK
+    assert not result.stderr.lines
+
+
+def test_disable_loggers_does_not_propagate(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+    import logging
+    import os
+
+    parent_logger = logging.getLogger("parent")
+    child_logger = parent_logger.getChild("child")
+
+    def test_logger_propagation_to_parent(caplog):
+            with caplog.at_level(logging.DEBUG):
+                parent_logger.warning("some parent logger message")
+                child_logger.warning("some child logger message")
+                assert len(caplog.record_tuples) == 1
+                assert caplog.record_tuples[0][0] == "parent"
+                assert caplog.record_tuples[0][2] == "some parent logger message"
+    """
+    )
+
+    result = pytester.runpytest("--log-disable=parent.child", "-s")
+    assert result.ret == ExitCode.OK
+    assert not result.stderr.lines
+
+
+def test_log_disabling_works_with_log_cli(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+    import logging
+    disabled_log = logging.getLogger('disabled')
+    test_log = logging.getLogger('test')
+
+    def test_log_cli_works(caplog):
+        test_log.info("Visible text!")
+        disabled_log.warning("This string will be suppressed.")
+    """
+    )
+    result = pytester.runpytest(
+        "--log-cli-level=DEBUG",
+        "--log-disable=disabled",
+    )
+    assert result.ret == ExitCode.OK
+    result.stdout.fnmatch_lines(
+        "INFO     test:test_log_disabling_works_with_log_cli.py:6 Visible text!"
+    )
+    result.stdout.no_fnmatch_line(
+        "WARNING  disabled:test_log_disabling_works_with_log_cli.py:7 This string will be suppressed."
+    )
+    assert not result.stderr.lines
+
+
+def test_without_date_format_log(pytester: Pytester) -> None:
+    """Check that date is not printed by default."""
+    pytester.makepyfile(
+        """
+        import logging
+
+        logger = logging.getLogger(__name__)
+
+        def test_foo():
+            logger.warning('text')
+            assert False
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(
+        ["WARNING  test_without_date_format_log:test_without_date_format_log.py:6 text"]
+    )
+
+
+def test_date_format_log(pytester: Pytester) -> None:
+    """Check that log_date_format affects output."""
+    pytester.makepyfile(
+        """
+        import logging
+
+        logger = logging.getLogger(__name__)
+
+        def test_foo():
+            logger.warning('text')
+            assert False
+        """
+    )
+    pytester.makeini(
+        """
+        [pytest]
+        log_format=%(asctime)s; %(levelname)s; %(message)s
+        log_date_format=%Y-%m-%d %H:%M:%S
+    """
+    )
+    result = pytester.runpytest()
+    assert result.ret == 1
+    result.stdout.re_match_lines([r"^[0-9-]{10} [0-9:]{8}; WARNING; text"])
+
+
+def test_date_format_percentf_log(pytester: Pytester) -> None:
+    """Make sure that microseconds are printed in log."""
+    pytester.makepyfile(
+        """
+        import logging
+
+        logger = logging.getLogger(__name__)
+
+        def test_foo():
+            logger.warning('text')
+            assert False
+        """
+    )
+    pytester.makeini(
+        """
+        [pytest]
+        log_format=%(asctime)s; %(levelname)s; %(message)s
+        log_date_format=%Y-%m-%d %H:%M:%S.%f
+    """
+    )
+    result = pytester.runpytest()
+    assert result.ret == 1
+    result.stdout.re_match_lines([r"^[0-9-]{10} [0-9:]{8}.[0-9]{6}; WARNING; text"])
+
+
+def test_date_format_percentf_tz_log(pytester: Pytester) -> None:
+    """Make sure that timezone and microseconds are properly formatted together."""
+    pytester.makepyfile(
+        """
+        import logging
+
+        logger = logging.getLogger(__name__)
+
+        def test_foo():
+            logger.warning('text')
+            assert False
+        """
+    )
+    pytester.makeini(
+        """
+        [pytest]
+        log_format=%(asctime)s; %(levelname)s; %(message)s
+        log_date_format=%Y-%m-%d %H:%M:%S.%f%z
+    """
+    )
+    result = pytester.runpytest()
+    assert result.ret == 1
+    result.stdout.re_match_lines(
+        [r"^[0-9-]{10} [0-9:]{8}.[0-9]{6}[+-][0-9\.]+; WARNING; text"]
+    )
+
+
+def test_log_file_cli_fallback_options(pytester: Pytester) -> None:
+    """Make sure that fallback values for log-file formats and level works."""
+    pytester.makepyfile(
+        """
+        import logging
+        logger = logging.getLogger()
+
+        def test_foo():
+            logger.info('info text going to logger')
+            logger.warning('warning text going to logger')
+            logger.error('error text going to logger')
+
+            assert 0
+    """
+    )
+    log_file = str(pytester.path.joinpath("pytest.log"))
+    result = pytester.runpytest(
+        "--log-level=ERROR",
+        "--log-format=%(asctime)s %(message)s",
+        "--log-date-format=%H:%M",
+        "--log-file=pytest.log",
+    )
+    assert result.ret == 1
+
+    # The log file should only contain the error log messages
+    # not the warning or info ones and the format and date format
+    # should match the formats provided using --log-format and --log-date-format
+    assert os.path.isfile(log_file)
+    with open(log_file, encoding="utf-8") as rfh:
+        contents = rfh.read()
+        assert re.match(r"[0-9]{2}:[0-9]{2} error text going to logger\s*", contents)
+        assert "info text going to logger" not in contents
+        assert "warning text going to logger" not in contents
+        assert "error text going to logger" in contents
+
+    # Try with a different format and date format to make sure that the formats
+    # are being used
+    result = pytester.runpytest(
+        "--log-level=ERROR",
+        "--log-format=%(asctime)s : %(message)s",
+        "--log-date-format=%H:%M:%S",
+        "--log-file=pytest.log",
+    )
+    assert result.ret == 1
+
+    # The log file should only contain the error log messages
+    # not the warning or info ones and the format and date format
+    # should match the formats provided using --log-format and --log-date-format
+    assert os.path.isfile(log_file)
+    with open(log_file, encoding="utf-8") as rfh:
+        contents = rfh.read()
+        assert re.match(
+            r"[0-9]{2}:[0-9]{2}:[0-9]{2} : error text going to logger\s*", contents
+        )
+        assert "info text going to logger" not in contents
+        assert "warning text going to logger" not in contents
+        assert "error text going to logger" in contents
diff --git a/testing/plugins_integration/bdd_wallet.py b/testing/plugins_integration/bdd_wallet.py
index 35927ea5875..d748028842a 100644
--- a/testing/plugins_integration/bdd_wallet.py
+++ b/testing/plugins_integration/bdd_wallet.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from pytest_bdd import given
 from pytest_bdd import scenario
 from pytest_bdd import then
diff --git a/testing/plugins_integration/django_settings.py b/testing/plugins_integration/django_settings.py
index 0715f476531..e36e554db9a 100644
--- a/testing/plugins_integration/django_settings.py
+++ b/testing/plugins_integration/django_settings.py
@@ -1 +1,4 @@
+from __future__ import annotations
+
+
 SECRET_KEY = "mysecret"
diff --git a/testing/plugins_integration/pytest.ini b/testing/plugins_integration/pytest.ini
index b42b07d145a..86058fbbac8 100644
--- a/testing/plugins_integration/pytest.ini
+++ b/testing/plugins_integration/pytest.ini
@@ -1,5 +1,7 @@
 [pytest]
 addopts = --strict-markers
+asyncio_mode = strict
 filterwarnings =
     error::pytest.PytestWarning
+    ignore:usefixtures.* without arguments has no effect:pytest.PytestWarning
     ignore:.*.fspath is deprecated and will be replaced by .*.path.*:pytest.PytestDeprecationWarning
diff --git a/testing/plugins_integration/pytest_anyio_integration.py b/testing/plugins_integration/pytest_anyio_integration.py
index 65c2f593663..41ffad18a6e 100644
--- a/testing/plugins_integration/pytest_anyio_integration.py
+++ b/testing/plugins_integration/pytest_anyio_integration.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import anyio
 
 import pytest
diff --git a/testing/plugins_integration/pytest_asyncio_integration.py b/testing/plugins_integration/pytest_asyncio_integration.py
index 5d2a3faccfc..cef67f83ea6 100644
--- a/testing/plugins_integration/pytest_asyncio_integration.py
+++ b/testing/plugins_integration/pytest_asyncio_integration.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import asyncio
 
 import pytest
diff --git a/testing/plugins_integration/pytest_mock_integration.py b/testing/plugins_integration/pytest_mock_integration.py
index 740469d00fb..a49129cf0c9 100644
--- a/testing/plugins_integration/pytest_mock_integration.py
+++ b/testing/plugins_integration/pytest_mock_integration.py
@@ -1,2 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+
 def test_mocker(mocker):
     mocker.MagicMock()
diff --git a/testing/plugins_integration/pytest_rerunfailures_integration.py b/testing/plugins_integration/pytest_rerunfailures_integration.py
new file mode 100644
index 00000000000..449661f7294
--- /dev/null
+++ b/testing/plugins_integration/pytest_rerunfailures_integration.py
@@ -0,0 +1,13 @@
+from __future__ import annotations
+
+import unittest
+
+
+class MyTestCase(unittest.TestCase):
+    first_time = True
+
+    def test_fail_the_first_time(self) -> None:
+        """Regression test for issue #12424."""
+        if self.first_time:
+            type(self).first_time = False
+            self.fail()
diff --git a/testing/plugins_integration/pytest_trio_integration.py b/testing/plugins_integration/pytest_trio_integration.py
index 199f7850bc4..eceac5076a9 100644
--- a/testing/plugins_integration/pytest_trio_integration.py
+++ b/testing/plugins_integration/pytest_trio_integration.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import trio
 
 import pytest
diff --git a/testing/plugins_integration/pytest_twisted_integration.py b/testing/plugins_integration/pytest_twisted_integration.py
index 94748d036e5..4f386bf1b9f 100644
--- a/testing/plugins_integration/pytest_twisted_integration.py
+++ b/testing/plugins_integration/pytest_twisted_integration.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest_twisted
 from twisted.internet.task import deferLater
 
diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt
index 90b253cc6d3..93e3dc7d0b1 100644
--- a/testing/plugins_integration/requirements.txt
+++ b/testing/plugins_integration/requirements.txt
@@ -1,15 +1,15 @@
-anyio[curio,trio]==3.4.0
-django==3.2.9
-pytest-asyncio==0.16.0
-pytest-bdd==5.0.0
-pytest-cov==3.0.0
-pytest-django==4.5.1
+anyio[trio]==4.9.0
+django==5.1.7
+pytest-asyncio==0.26.0
+pytest-bdd==8.1.0
+pytest-cov==6.1.1
+pytest-django==4.11.1
 pytest-flakes==4.0.5
-pytest-html==3.1.1
-pytest-mock==3.6.1
-pytest-rerunfailures==10.2
-pytest-sugar==0.9.4
-pytest-trio==0.7.0
-pytest-twisted==1.13.4
-twisted==21.7.0
-pytest-xvfb==2.0.0
+pytest-html==4.1.1
+pytest-mock==3.14.0
+pytest-rerunfailures==15.0
+pytest-sugar==1.0.0
+pytest-trio==0.8.0
+pytest-twisted==1.14.3
+twisted==24.11.0
+pytest-xvfb==3.1.1
diff --git a/testing/plugins_integration/simple_integration.py b/testing/plugins_integration/simple_integration.py
index 20b2fc4b5bb..ed504ae4bf1 100644
--- a/testing/plugins_integration/simple_integration.py
+++ b/testing/plugins_integration/simple_integration.py
@@ -1,3 +1,6 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import pytest
 
 
diff --git a/testing/python/approx.py b/testing/python/approx.py
index 0d411d8a6da..75b57b6965c 100644
--- a/testing/python/approx.py
+++ b/testing/python/approx.py
@@ -1,16 +1,20 @@
-import operator
-import sys
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from contextlib import contextmanager
 from decimal import Decimal
 from fractions import Fraction
+from math import sqrt
+import operator
 from operator import eq
 from operator import ne
-from typing import Optional
 
-import pytest
 from _pytest.pytester import Pytester
+from _pytest.python_api import _recursive_sequence_map
+import pytest
 from pytest import approx
 
+
 inf, nan = float("inf"), float("nan")
 
 
@@ -36,9 +40,7 @@ def set_continue(self):
     class MyDocTestRunner(doctest.DocTestRunner):
         def report_failure(self, out, test, example, got):
             raise AssertionError(
-                "'{}' evaluates to '{}', not '{}'".format(
-                    example.source.strip(), got.strip(), example.want.strip()
-                )
+                f"'{example.source.strip()}' evaluates to '{got.strip()}', not '{example.want.strip()}'"
             )
 
     return MyDocTestRunner()
@@ -88,21 +90,34 @@ def do_assert(lhs, rhs, expected_message, verbosity_level=0):
     return do_assert
 
 
-SOME_FLOAT = r"[+-]?([0-9]*[.])?[0-9]+\s*"
+SOME_FLOAT = r"[+-]?((?:([0-9]*[.])?[0-9]+(e-?[0-9]+)?)|inf|nan)\s*"
 SOME_INT = r"[0-9]+\s*"
+SOME_TOLERANCE = rf"({SOME_FLOAT}|[+-]?[0-9]+(\.[0-9]+)?[eE][+-]?[0-9]+\s*)"
 
 
 class TestApprox:
-    def test_error_messages(self, assert_approx_raises_regex):
-        np = pytest.importorskip("numpy")
-
+    def test_error_messages_native_dtypes(self, assert_approx_raises_regex):
+        # Treat bool exactly.
+        assert_approx_raises_regex(
+            {"a": 1.0, "b": True},
+            {"a": 1.0, "b": False},
+            [
+                "",
+                "  comparison failed. Mismatched elements: 1 / 2:",
+                f"  Max absolute difference: {SOME_FLOAT}",
+                f"  Max relative difference: {SOME_FLOAT}",
+                r"  Index\s+\| Obtained\s+\| Expected",
+                r".*(True|False)\s+",
+            ],
+        )
         assert_approx_raises_regex(
             2.0,
             1.0,
             [
+                "",
                 "  comparison failed",
                 f"  Obtained: {SOME_FLOAT}",
-                f"  Expected: {SOME_FLOAT} ± {SOME_FLOAT}",
+                f"  Expected: {SOME_FLOAT} ± {SOME_TOLERANCE}",
             ],
         )
 
@@ -114,12 +129,31 @@ def test_error_messages(self, assert_approx_raises_regex):
                 "c": 3000000.0,
             },
             [
+                r"",
                 r"  comparison failed. Mismatched elements: 2 / 3:",
                 rf"  Max absolute difference: {SOME_FLOAT}",
                 rf"  Max relative difference: {SOME_FLOAT}",
-                r"  Index \| Obtained\s+\| Expected           ",
-                rf"  a     \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
-                rf"  c     \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
+                r"  Index \| Obtained\s+\| Expected\s+",
+                rf"  a     \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_TOLERANCE}",
+                rf"  c     \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_TOLERANCE}",
+            ],
+        )
+
+        assert_approx_raises_regex(
+            {"a": 1.0, "b": None, "c": None},
+            {
+                "a": None,
+                "b": 1000.0,
+                "c": None,
+            },
+            [
+                r"",
+                r"  comparison failed. Mismatched elements: 2 / 3:",
+                r"  Max absolute difference: -inf",
+                r"  Max relative difference: -inf",
+                r"  Index \| Obtained\s+\| Expected\s+",
+                rf"  a     \| {SOME_FLOAT} \| None",
+                rf"  b     \| None\s+\| {SOME_FLOAT} ± {SOME_FLOAT}",
             ],
         )
 
@@ -127,6 +161,7 @@ def test_error_messages(self, assert_approx_raises_regex):
             [1.0, 2.0, 3.0, 4.0],
             [1.0, 3.0, 3.0, 5.0],
             [
+                r"",
                 r"  comparison failed. Mismatched elements: 2 / 4:",
                 rf"  Max absolute difference: {SOME_FLOAT}",
                 rf"  Max relative difference: {SOME_FLOAT}",
@@ -136,6 +171,36 @@ def test_error_messages(self, assert_approx_raises_regex):
             ],
         )
 
+        assert_approx_raises_regex(
+            (1, 2.2, 4),
+            (1, 3.2, 4),
+            [
+                r"",
+                r"  comparison failed. Mismatched elements: 1 / 3:",
+                rf"  Max absolute difference: {SOME_FLOAT}",
+                rf"  Max relative difference: {SOME_FLOAT}",
+                r"  Index \| Obtained\s+\| Expected   ",
+                rf"  1     \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
+            ],
+        )
+
+        # Specific test for comparison with 0.0 (relative diff will be 'inf')
+        assert_approx_raises_regex(
+            [0.0],
+            [1.0],
+            [
+                r"",
+                r"  comparison failed. Mismatched elements: 1 / 1:",
+                rf"  Max absolute difference: {SOME_FLOAT}",
+                r"  Max relative difference: inf",
+                r"  Index \| Obtained\s+\| Expected   ",
+                rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
+            ],
+        )
+
+    def test_error_messages_numpy_dtypes(self, assert_approx_raises_regex):
+        np = pytest.importorskip("numpy")
+
         a = np.linspace(0, 100, 20)
         b = np.linspace(0, 100, 20)
         a[10] += 0.5
@@ -143,6 +208,7 @@ def test_error_messages(self, assert_approx_raises_regex):
             a,
             b,
             [
+                r"",
                 r"  comparison failed. Mismatched elements: 1 / 20:",
                 rf"  Max absolute difference: {SOME_FLOAT}",
                 rf"  Max relative difference: {SOME_FLOAT}",
@@ -165,6 +231,7 @@ def test_error_messages(self, assert_approx_raises_regex):
                 ]
             ),
             [
+                r"",
                 r"  comparison failed. Mismatched elements: 3 / 8:",
                 rf"  Max absolute difference: {SOME_FLOAT}",
                 rf"  Max relative difference: {SOME_FLOAT}",
@@ -176,22 +243,11 @@ def test_error_messages(self, assert_approx_raises_regex):
         )
 
         # Specific test for comparison with 0.0 (relative diff will be 'inf')
-        assert_approx_raises_regex(
-            [0.0],
-            [1.0],
-            [
-                r"  comparison failed. Mismatched elements: 1 / 1:",
-                rf"  Max absolute difference: {SOME_FLOAT}",
-                r"  Max relative difference: inf",
-                r"  Index \| Obtained\s+\| Expected   ",
-                rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
-            ],
-        )
-
         assert_approx_raises_regex(
             np.array([0.0]),
             np.array([1.0]),
             [
+                r"",
                 r"  comparison failed. Mismatched elements: 1 / 1:",
                 rf"  Max absolute difference: {SOME_FLOAT}",
                 r"  Max relative difference: inf",
@@ -209,6 +265,7 @@ def test_error_messages_invalid_args(self, assert_approx_raises_regex):
         message = "\n".join(str(e.value).split("\n")[1:])
         assert message == "\n".join(
             [
+                "  ",
                 "  Impossible to compare arrays with different shapes.",
                 "  Shapes: (2, 1) and (2, 2)",
             ]
@@ -219,6 +276,7 @@ def test_error_messages_invalid_args(self, assert_approx_raises_regex):
         message = "\n".join(str(e.value).split("\n")[1:])
         assert message == "\n".join(
             [
+                "  ",
                 "  Impossible to compare lists with different sizes.",
                 "  Lengths: 2 and 3",
             ]
@@ -232,6 +290,7 @@ def test_error_messages_with_different_verbosity(self, assert_approx_raises_rege
                 2.0,
                 1.0,
                 [
+                    "",
                     "  comparison failed",
                     f"  Obtained: {SOME_FLOAT}",
                     f"  Expected: {SOME_FLOAT} ± {SOME_FLOAT}",
@@ -245,15 +304,15 @@ def test_error_messages_with_different_verbosity(self, assert_approx_raises_rege
             a,
             b,
             [
-                r"  comparison failed. Mismatched elements: 20 / 20:",
-                rf"  Max absolute difference: {SOME_FLOAT}",
-                rf"  Max relative difference: {SOME_FLOAT}",
-                r"  Index \| Obtained\s+\| Expected",
-                rf"  \(0,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
-                rf"  \(1,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
-                rf"  \(2,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}...",
-                "",
-                rf"\s*...Full output truncated \({SOME_INT} lines hidden\), use '-vv' to show",
+                r"^  $",
+                r"^  comparison failed. Mismatched elements: 20 / 20:$",
+                rf"^  Max absolute difference: {SOME_FLOAT}$",
+                rf"^  Max relative difference: {SOME_FLOAT}$",
+                r"^  Index \| Obtained\s+\| Expected\s+$",
+                rf"^  \(0,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}e-{SOME_INT}$",
+                rf"^  \(1,\)\s+\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}e-{SOME_INT}\.\.\.$",
+                "^  $",
+                rf"^  ...Full output truncated \({SOME_INT} lines hidden\), use '-vv' to show$",
             ],
             verbosity_level=0,
         )
@@ -262,6 +321,7 @@ def test_error_messages_with_different_verbosity(self, assert_approx_raises_rege
             a,
             b,
             [
+                r"  ",
                 r"  comparison failed. Mismatched elements: 20 / 20:",
                 rf"  Max absolute difference: {SOME_FLOAT}",
                 rf"  Max relative difference: {SOME_FLOAT}",
@@ -288,6 +348,11 @@ def test_repr_string(self):
             "approx({'b': 2.0 ± 2.0e-06, 'a': 1.0 ± 1.0e-06})",
         )
 
+        assert repr(approx(42, abs=1)) == "42 ± 1"
+        assert repr(approx(5, rel=0.01)) == "5 ± 0.05"
+        assert repr(approx(24000, abs=500)) == "24000 ± 500"
+        assert repr(approx(1500, abs=555)) == "1500 ± 555"
+
     def test_repr_complex_numbers(self):
         assert repr(approx(inf + 1j)) == "(inf+1j)"
         assert repr(approx(1.0j, rel=inf)) == "1j ± inf"
@@ -301,7 +366,7 @@ def test_repr_complex_numbers(self):
         assert repr(approx(3 + 4 * 1j)) == "(3+4j) ± 5.0e-06 ∠ ±180°"
 
         # absolute tolerance is not scaled
-        assert repr(approx(3.3 + 4.4 * 1j, abs=0.02)) == "(3.3+4.4j) ± 2.0e-02 ∠ ±180°"
+        assert repr(approx(3.3 + 4.4 * 1j, abs=0.02)) == "(3.3+4.4j) ± 0.02 ∠ ±180°"
 
     @pytest.mark.parametrize(
         "value, expected_repr_string",
@@ -325,6 +390,37 @@ def test_bool(self):
 
         assert err.match(r"approx\(\) is not supported in a boolean context")
 
+    def test_mixed_sequence(self, assert_approx_raises_regex) -> None:
+        """Approx should work on sequences that also contain non-numbers (#13010)."""
+        assert_approx_raises_regex(
+            [1.1, 2, "word"],
+            [1.0, 2, "different"],
+            [
+                "",
+                r"  comparison failed. Mismatched elements: 2 / 3:",
+                rf"  Max absolute difference: {SOME_FLOAT}",
+                rf"  Max relative difference: {SOME_FLOAT}",
+                r"  Index \| Obtained\s+\| Expected\s+",
+                r"\s*0\s*\|\s*1\.1\s*\|\s*1\.0\s*±\s*1\.0e\-06\s*",
+                r"\s*2\s*\|\s*word\s*\|\s*different\s*",
+            ],
+            verbosity_level=2,
+        )
+        assert_approx_raises_regex(
+            [1.1, 2, "word"],
+            [1.0, 2, "word"],
+            [
+                "",
+                r"  comparison failed. Mismatched elements: 1 / 3:",
+                rf"  Max absolute difference: {SOME_FLOAT}",
+                rf"  Max relative difference: {SOME_FLOAT}",
+                r"  Index \| Obtained\s+\| Expected\s+",
+                r"\s*0\s*\|\s*1\.1\s*\|\s*1\.0\s*±\s*1\.0e\-06\s*",
+            ],
+            verbosity_level=2,
+        )
+        assert [1.1, 2, "word"] == pytest.approx([1.1, 2, "word"])
+
     def test_operator_overloading(self):
         assert 1 == approx(1, rel=1e-6, abs=1e-12)
         assert not (1 != approx(1, rel=1e-6, abs=1e-12))
@@ -370,9 +466,7 @@ def test_zero_tolerance(self):
             (-1e100, -1e100),
         ],
     )
-    def test_negative_tolerance(
-        self, rel: Optional[float], abs: Optional[float]
-    ) -> None:
+    def test_negative_tolerance(self, rel: float | None, abs: float | None) -> None:
         # Negative tolerances are not allowed.
         with pytest.raises(ValueError):
             1.1 == approx(1, rel, abs)
@@ -546,6 +640,22 @@ def test_complex(self):
             assert approx(x, rel=5e-6, abs=0) == a
             assert approx(x, rel=5e-7, abs=0) != a
 
+    def test_expecting_bool(self) -> None:
+        assert True == approx(True)  # noqa: E712
+        assert False == approx(False)  # noqa: E712
+        assert True != approx(False)  # noqa: E712
+        assert True != approx(False, abs=2)  # noqa: E712
+        assert 1 != approx(True)
+
+    def test_expecting_bool_numpy(self) -> None:
+        """Check approx comparing with numpy.bool (#13047)."""
+        np = pytest.importorskip("numpy")
+        assert np.False_ != approx(True)
+        assert np.True_ != approx(False)
+        assert np.True_ == approx(True)
+        assert np.False_ == approx(False)
+        assert np.True_ != approx(False, abs=2)
+
     def test_list(self):
         actual = [1 + 1e-7, 2 + 1e-8]
         expected = [1, 2]
@@ -611,10 +721,25 @@ def test_dict_wrong_len(self):
     def test_dict_nonnumeric(self):
         assert {"a": 1.0, "b": None} == pytest.approx({"a": 1.0, "b": None})
         assert {"a": 1.0, "b": 1} != pytest.approx({"a": 1.0, "b": None})
+        assert {"a": 1.0, "b": True} != pytest.approx({"a": 1.0, "b": False}, abs=2)
 
     def test_dict_vs_other(self):
         assert 1 != approx({"a": 0})
 
+    def test_dict_for_div_by_zero(self, assert_approx_raises_regex):
+        assert_approx_raises_regex(
+            {"foo": 42.0},
+            {"foo": 0.0},
+            [
+                r"",
+                r"  comparison failed. Mismatched elements: 1 / 1:",
+                rf"  Max absolute difference: {SOME_FLOAT}",
+                r"  Max relative difference: inf",
+                r"  Index \| Obtained\s+\| Expected   ",
+                rf"  foo   | {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
+            ],
+        )
+
     def test_numpy_array(self):
         np = pytest.importorskip("numpy")
 
@@ -704,6 +829,23 @@ def test_numpy_array_wrong_shape(self):
         assert a12 != approx(a21)
         assert a21 != approx(a12)
 
+    def test_numpy_array_implicit_conversion(self):
+        np = pytest.importorskip("numpy")
+
+        class ImplicitArray:
+            """Type which is implicitly convertible to a numpy array."""
+
+            def __init__(self, vals):
+                self.vals = vals
+
+            def __array__(self, dtype=None, copy=None):
+                return np.array(self.vals)
+
+        vec1 = ImplicitArray([1.0, 2.0, 3.0])
+        vec2 = ImplicitArray([1.0, 2.0, 4.0])
+        # see issue #12114 for test case
+        assert vec1 != approx(vec2)
+
     def test_numpy_array_protocol(self):
         """
         array-like objects such as tensorflow's DeviceArray are handled like ndarray.
@@ -772,7 +914,7 @@ def test_foo():
     def test_expected_value_type_error(self, x, name):
         with pytest.raises(
             TypeError,
-            match=fr"pytest.approx\(\) does not support nested {name}:",
+            match=rf"pytest.approx\(\) does not support nested {name}:",
         ):
             approx(x)
 
@@ -797,7 +939,7 @@ def test_nonnumeric_okay_if_equal(self, x):
         ],
     )
     def test_nonnumeric_false_if_unequal(self, x):
-        """For nonnumeric types, x != pytest.approx(y) reduces to x != y"""
+        """For non-numeric types, x != pytest.approx(y) reduces to x != y"""
         assert "ab" != approx("abc")
         assert ["ab"] != approx(["abc"])
         # in particular, both of these should return False
@@ -810,7 +952,6 @@ def test_nonnumeric_false_if_unequal(self, x):
         assert 1.0 != approx([None])
         assert None != approx([1.0])  # noqa: E711
 
-    @pytest.mark.skipif(sys.version_info < (3, 7), reason="requires ordered dicts")
     def test_nonnumeric_dict_repr(self):
         """Dicts with non-numerics and infinites have no tolerances"""
         x1 = {"foo": 1.0000005, "bar": None, "foobar": inf}
@@ -860,13 +1001,89 @@ def test_numpy_scalar_with_array(self):
         assert approx(expected, rel=5e-7, abs=0) == actual
         assert approx(expected, rel=5e-8, abs=0) != actual
 
-    def test_generic_sized_iterable_object(self):
-        class MySizedIterable:
-            def __iter__(self):
-                return iter([1, 2, 3, 4])
+    def test_generic_ordered_sequence(self):
+        class MySequence:
+            def __getitem__(self, i):
+                return [1, 2, 3, 4][i]
 
             def __len__(self):
                 return 4
 
-        expected = MySizedIterable()
-        assert [1, 2, 3, 4] == approx(expected)
+        expected = MySequence()
+        assert [1, 2, 3, 4] == approx(expected, abs=1e-4)
+
+        expected_repr = "approx([1 ± 1.0e-06, 2 ± 2.0e-06, 3 ± 3.0e-06, 4 ± 4.0e-06])"
+        assert repr(approx(expected)) == expected_repr
+
+    def test_allow_ordered_sequences_only(self) -> None:
+        """pytest.approx() should raise an error on unordered sequences (#9692)."""
+        with pytest.raises(TypeError, match="only supports ordered sequences"):
+            assert {1, 2, 3} == approx({1, 2, 3})
+
+    def test_strange_sequence(self):
+        """https://github.com/pytest-dev/pytest/issues/11797"""
+        a = MyVec3(1, 2, 3)
+        b = MyVec3(0, 1, 2)
+
+        # this would trigger the error inside the test
+        pytest.approx(a, abs=0.5)._repr_compare(b)
+
+        assert b == pytest.approx(a, abs=2)
+        assert b != pytest.approx(a, abs=0.5)
+
+
+class MyVec3:  # incomplete
+    """sequence like"""
+
+    _x: int
+    _y: int
+    _z: int
+
+    def __init__(self, x: int, y: int, z: int):
+        self._x, self._y, self._z = x, y, z
+
+    def __repr__(self) -> str:
+        return f"<MyVec3 {self._x} {self._y} {self._z}>"
+
+    def __len__(self) -> int:
+        return 3
+
+    def __getitem__(self, key: int) -> int:
+        if key == 0:
+            return self._x
+        if key == 1:
+            return self._y
+        if key == 2:
+            return self._z
+        raise IndexError(key)
+
+
+class TestRecursiveSequenceMap:
+    def test_map_over_scalar(self):
+        assert _recursive_sequence_map(sqrt, 16) == 4
+
+    def test_map_over_empty_list(self):
+        assert _recursive_sequence_map(sqrt, []) == []
+
+    def test_map_over_list(self):
+        assert _recursive_sequence_map(sqrt, [4, 16, 25, 676]) == [2, 4, 5, 26]
+
+    def test_map_over_tuple(self):
+        assert _recursive_sequence_map(sqrt, (4, 16, 25, 676)) == (2, 4, 5, 26)
+
+    def test_map_over_nested_lists(self):
+        assert _recursive_sequence_map(sqrt, [4, [25, 64], [[49]]]) == [
+            2,
+            [5, 8],
+            [[7]],
+        ]
+
+    def test_map_over_mixed_sequence(self):
+        assert _recursive_sequence_map(sqrt, [4, (25, 64), [(49)]]) == [
+            2,
+            (5, 8),
+            [(7)],
+        ]
+
+    def test_map_over_sequence_like(self):
+        assert _recursive_sequence_map(int, MyVec3(1, 2, 3)) == [1, 2, 3]
diff --git a/testing/python/collect.py b/testing/python/collect.py
index ac3edd395ab..530f1c340ff 100644
--- a/testing/python/collect.py
+++ b/testing/python/collect.py
@@ -1,11 +1,12 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import os
 import sys
 import textwrap
 from typing import Any
-from typing import Dict
 
 import _pytest._code
-import pytest
 from _pytest.config import ExitCode
 from _pytest.main import Session
 from _pytest.monkeypatch import MonkeyPatch
@@ -13,6 +14,7 @@
 from _pytest.pytester import Pytester
 from _pytest.python import Class
 from _pytest.python import Function
+import pytest
 
 
 class TestModule:
@@ -35,9 +37,9 @@ def test_import_duplicate(self, pytester: Pytester) -> None:
             [
                 "*import*mismatch*",
                 "*imported*test_whatever*",
-                "*%s*" % p1,
+                f"*{p1}*",
                 "*not the same*",
-                "*%s*" % p2,
+                f"*{p2}*",
                 "*HINT*",
             ]
         )
@@ -53,14 +55,13 @@ def test_import_prepend_append(
         monkeypatch.syspath_prepend(str(root1))
         p.write_text(
             textwrap.dedent(
-                """\
+                f"""\
                 import x456
                 def test():
-                    assert x456.__file__.startswith({!r})
-                """.format(
-                    str(root2)
-                )
-            )
+                    assert x456.__file__.startswith({str(root2)!r})
+                """
+            ),
+            encoding="utf-8",
         )
         with monkeypatch.context() as mp:
             mp.chdir(root2)
@@ -262,6 +263,69 @@ def prop(self):
         result = pytester.runpytest()
         assert result.ret == ExitCode.NO_TESTS_COLLECTED
 
+    def test_does_not_discover_properties(self, pytester: Pytester) -> None:
+        """Regression test for #12446."""
+        pytester.makepyfile(
+            """\
+            class TestCase:
+                @property
+                def oops(self):
+                    raise SystemExit('do not call me!')
+            """
+        )
+        result = pytester.runpytest()
+        assert result.ret == ExitCode.NO_TESTS_COLLECTED
+
+    def test_does_not_discover_instance_descriptors(self, pytester: Pytester) -> None:
+        """Regression test for #12446."""
+        pytester.makepyfile(
+            """\
+            # not `@property`, but it acts like one
+            # this should cover the case of things like `@cached_property` / etc.
+            class MyProperty:
+                def __init__(self, func):
+                    self._func = func
+                def __get__(self, inst, owner):
+                    if inst is None:
+                        return self
+                    else:
+                        return self._func.__get__(inst, owner)()
+
+            class TestCase:
+                @MyProperty
+                def oops(self):
+                    raise SystemExit('do not call me!')
+            """
+        )
+        result = pytester.runpytest()
+        assert result.ret == ExitCode.NO_TESTS_COLLECTED
+
+    def test_abstract_class_is_not_collected(self, pytester: Pytester) -> None:
+        """Regression test for #12275 (non-unittest version)."""
+        pytester.makepyfile(
+            """
+            import abc
+
+            class TestBase(abc.ABC):
+                @abc.abstractmethod
+                def abstract1(self): pass
+
+                @abc.abstractmethod
+                def abstract2(self): pass
+
+                def test_it(self): pass
+
+            class TestPartial(TestBase):
+                def abstract1(self): pass
+
+            class TestConcrete(TestPartial):
+                def abstract2(self): pass
+            """
+        )
+        result = pytester.runpytest()
+        assert result.ret == ExitCode.OK
+        result.assert_outcomes(passed=1)
+
 
 class TestFunction:
     def test_getmodulecollector(self, pytester: Pytester) -> None:
@@ -775,13 +839,13 @@ def test_ordered_by_definition_order(self, pytester: Pytester) -> None:
         pytester.makepyfile(
             """\
             class Test1:
-                def test_foo(): pass
-                def test_bar(): pass
+                def test_foo(self): pass
+                def test_bar(self): pass
             class Test2:
-                def test_foo(): pass
+                def test_foo(self): pass
                 test_bar = Test1.test_bar
             class Test3(Test2):
-                def test_baz(): pass
+                def test_baz(self): pass
             """
         )
         result = pytester.runpytest("--collect-only")
@@ -826,13 +890,14 @@ def test_customized_pymakemodule_issue205_subdir(self, pytester: Pytester) -> No
             textwrap.dedent(
                 """\
                 import pytest
-                @pytest.hookimpl(hookwrapper=True)
+                @pytest.hookimpl(wrapper=True)
                 def pytest_pycollect_makemodule():
-                    outcome = yield
-                    mod = outcome.get_result()
+                    mod = yield
                     mod.obj.hello = "world"
+                    return mod
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         b.joinpath("test_module.py").write_text(
             textwrap.dedent(
@@ -840,7 +905,8 @@ def pytest_pycollect_makemodule():
                 def test_hello():
                     assert hello == "world"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=1)
@@ -852,16 +918,16 @@ def test_customized_pymakeitem(self, pytester: Pytester) -> None:
             textwrap.dedent(
                 """\
                 import pytest
-                @pytest.hookimpl(hookwrapper=True)
+                @pytest.hookimpl(wrapper=True)
                 def pytest_pycollect_makeitem():
-                    outcome = yield
-                    if outcome.excinfo is None:
-                        result = outcome.get_result()
-                        if result:
-                            for func in result:
-                                func._some123 = "world"
+                    result = yield
+                    if result:
+                        for func in result:
+                            func._some123 = "world"
+                    return result
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         b.joinpath("test_module.py").write_text(
             textwrap.dedent(
@@ -874,7 +940,8 @@ def obj(request):
                 def test_hello(obj):
                     assert obj == "world"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=1)
@@ -897,25 +964,29 @@ def pytest_pycollect_makeitem(collector, name, obj):
     def test_issue2369_collect_module_fileext(self, pytester: Pytester) -> None:
         """Ensure we can collect files with weird file extensions as Python
         modules (#2369)"""
-        # We'll implement a little finder and loader to import files containing
+        # Implement a little meta path finder to import files containing
         # Python source code whose file extension is ".narf".
         pytester.makeconftest(
             """
-            import sys, os, imp
+            import sys
+            import os.path
+            from importlib.util import spec_from_loader
+            from importlib.machinery import SourceFileLoader
             from _pytest.python import Module
 
-            class Loader(object):
-                def load_module(self, name):
-                    return imp.load_source(name, name + ".narf")
-            class Finder(object):
-                def find_module(self, name, path=None):
-                    if os.path.exists(name + ".narf"):
-                        return Loader()
-            sys.meta_path.append(Finder())
+            class MetaPathFinder:
+                def find_spec(self, fullname, path, target=None):
+                    if os.path.exists(fullname + ".narf"):
+                        return spec_from_loader(
+                            fullname,
+                            SourceFileLoader(fullname, fullname + ".narf"),
+                        )
+            sys.meta_path.append(MetaPathFinder())
 
             def pytest_collect_file(file_path, parent):
                 if file_path.suffix == ".narf":
-                    return Module.from_parent(path=file_path, parent=parent)"""
+                    return Module.from_parent(path=file_path, parent=parent)
+            """
         )
         pytester.makefile(
             ".narf",
@@ -970,7 +1041,8 @@ def pytest_runtest_call(item):
             def pytest_runtest_teardown(item):
                 assert item.path.stem == "test_in_sub1"
             """
-        )
+        ),
+        encoding="utf-8",
     )
     sub2.joinpath("conftest.py").write_text(
         textwrap.dedent(
@@ -983,10 +1055,11 @@ def pytest_runtest_call(item):
             def pytest_runtest_teardown(item):
                 assert item.path.stem == "test_in_sub2"
             """
-        )
+        ),
+        encoding="utf-8",
     )
-    sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass")
-    sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass")
+    sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass", encoding="utf-8")
+    sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass", encoding="utf-8")
     result = pytester.runpytest("-v", "-s")
     result.assert_outcomes(passed=2)
 
@@ -1003,9 +1076,9 @@ def test_skip_simple(self):
         with pytest.raises(pytest.skip.Exception) as excinfo:
             pytest.skip("xxx")
         assert excinfo.traceback[-1].frame.code.name == "skip"
-        assert excinfo.traceback[-1].ishidden()
+        assert excinfo.traceback[-1].ishidden(excinfo)
         assert excinfo.traceback[-2].frame.code.name == "test_skip_simple"
-        assert not excinfo.traceback[-2].ishidden()
+        assert not excinfo.traceback[-2].ishidden(excinfo)
 
     def test_traceback_argsetup(self, pytester: Pytester) -> None:
         pytester.makeconftest(
@@ -1094,7 +1167,7 @@ def test_filter_traceback_generated_code(self) -> None:
 
         tb = None
         try:
-            ns: Dict[str, Any] = {}
+            ns: dict[str, Any] = {}
             exec("def foo(): raise ValueError", ns)
             ns["foo"]()
         except ValueError:
@@ -1200,7 +1273,7 @@ def test_bar(self):
         classcol = pytester.collect_by_name(modcol, "TestClass")
         assert isinstance(classcol, Class)
         path, lineno, msg = classcol.reportinfo()
-        func = list(classcol.collect())[0]
+        func = next(iter(classcol.collect()))
         assert isinstance(func, Function)
         path, lineno, msg = func.reportinfo()
 
@@ -1374,7 +1447,8 @@ def test_skip_duplicates_by_default(pytester: Pytester) -> None:
             def test_real():
                 pass
             """
-        )
+        ),
+        encoding="utf-8",
     )
     result = pytester.runpytest(str(a), str(a))
     result.stdout.fnmatch_lines(["*collected 1 item*"])
@@ -1394,7 +1468,8 @@ def test_keep_duplicates(pytester: Pytester) -> None:
             def test_real():
                 pass
             """
-        )
+        ),
+        encoding="utf-8",
     )
     result = pytester.runpytest("--keep-duplicates", str(a), str(a))
     result.stdout.fnmatch_lines(["*collected 2 item*"])
@@ -1407,10 +1482,15 @@ def test_package_collection_infinite_recursion(pytester: Pytester) -> None:
 
 
 def test_package_collection_init_given_as_argument(pytester: Pytester) -> None:
-    """Regression test for #3749"""
+    """Regression test for #3749, #8976, #9263, #9313.
+
+    Specifying an __init__.py file directly should collect only the __init__.py
+    Module, not the entire package.
+    """
     p = pytester.copy_example("collect/package_init_given_as_arg")
-    result = pytester.runpytest(p / "pkg" / "__init__.py")
-    result.stdout.fnmatch_lines(["*1 passed*"])
+    items, hookrecorder = pytester.inline_genitems(p / "pkg" / "__init__.py")
+    assert len(items) == 1
+    assert items[0].name == "test_init"
 
 
 def test_package_with_modules(pytester: Pytester) -> None:
@@ -1439,8 +1519,12 @@ def test_package_with_modules(pytester: Pytester) -> None:
     sub2_test = sub2.joinpath("test")
     sub2_test.mkdir(parents=True)
 
-    sub1_test.joinpath("test_in_sub1.py").write_text("def test_1(): pass")
-    sub2_test.joinpath("test_in_sub2.py").write_text("def test_2(): pass")
+    sub1_test.joinpath("test_in_sub1.py").write_text(
+        "def test_1(): pass", encoding="utf-8"
+    )
+    sub2_test.joinpath("test_in_sub2.py").write_text(
+        "def test_2(): pass", encoding="utf-8"
+    )
 
     # Execute from .
     result = pytester.runpytest("-v", "-s")
@@ -1484,10 +1568,117 @@ def test_package_ordering(pytester: Pytester) -> None:
     sub2_test = sub2.joinpath("test")
     sub2_test.mkdir(parents=True)
 
-    root.joinpath("Test_root.py").write_text("def test_1(): pass")
-    sub1.joinpath("Test_sub1.py").write_text("def test_2(): pass")
-    sub2_test.joinpath("test_sub2.py").write_text("def test_3(): pass")
+    root.joinpath("Test_root.py").write_text("def test_1(): pass", encoding="utf-8")
+    sub1.joinpath("Test_sub1.py").write_text("def test_2(): pass", encoding="utf-8")
+    sub2_test.joinpath("test_sub2.py").write_text(
+        "def test_3(): pass", encoding="utf-8"
+    )
 
     # Execute from .
     result = pytester.runpytest("-v", "-s")
     result.assert_outcomes(passed=3)
+
+
+def test_collection_hierarchy(pytester: Pytester) -> None:
+    """A general test checking that a filesystem hierarchy is collected as
+    expected in various scenarios.
+
+    top/
+    ├── aaa
+    │   ├── pkg
+    │   │   ├── __init__.py
+    │   │   └── test_pkg.py
+    │   └── test_aaa.py
+    ├── test_a.py
+    ├── test_b
+    │   ├── __init__.py
+    │   └── test_b.py
+    ├── test_c.py
+    └── zzz
+        ├── dir
+        │   └── test_dir.py
+        ├── __init__.py
+        └── test_zzz.py
+    """
+    pytester.makepyfile(
+        **{
+            "top/aaa/test_aaa.py": "def test_it(): pass",
+            "top/aaa/pkg/__init__.py": "",
+            "top/aaa/pkg/test_pkg.py": "def test_it(): pass",
+            "top/test_a.py": "def test_it(): pass",
+            "top/test_b/__init__.py": "",
+            "top/test_b/test_b.py": "def test_it(): pass",
+            "top/test_c.py": "def test_it(): pass",
+            "top/zzz/__init__.py": "",
+            "top/zzz/test_zzz.py": "def test_it(): pass",
+            "top/zzz/dir/test_dir.py": "def test_it(): pass",
+        }
+    )
+
+    full = [
+        "<Dir test_collection_hierarchy*>",
+        "  <Dir top>",
+        "    <Dir aaa>",
+        "      <Package pkg>",
+        "        <Module test_pkg.py>",
+        "          <Function test_it>",
+        "      <Module test_aaa.py>",
+        "        <Function test_it>",
+        "    <Module test_a.py>",
+        "      <Function test_it>",
+        "    <Package test_b>",
+        "      <Module test_b.py>",
+        "        <Function test_it>",
+        "    <Module test_c.py>",
+        "      <Function test_it>",
+        "    <Package zzz>",
+        "      <Dir dir>",
+        "        <Module test_dir.py>",
+        "          <Function test_it>",
+        "      <Module test_zzz.py>",
+        "        <Function test_it>",
+    ]
+    result = pytester.runpytest("--collect-only")
+    result.stdout.fnmatch_lines(full, consecutive=True)
+    result = pytester.runpytest("top", "--collect-only")
+    result.stdout.fnmatch_lines(full, consecutive=True)
+    result = pytester.runpytest("top", "top", "--collect-only")
+    result.stdout.fnmatch_lines(full, consecutive=True)
+
+    result = pytester.runpytest(
+        "top/aaa", "top/aaa/pkg", "--collect-only", "--keep-duplicates"
+    )
+    result.stdout.fnmatch_lines(
+        [
+            "<Dir test_collection_hierarchy*>",
+            "  <Dir top>",
+            "    <Dir aaa>",
+            "      <Package pkg>",
+            "        <Module test_pkg.py>",
+            "          <Function test_it>",
+            "      <Module test_aaa.py>",
+            "        <Function test_it>",
+            "      <Package pkg>",
+            "        <Module test_pkg.py>",
+            "          <Function test_it>",
+        ],
+        consecutive=True,
+    )
+
+    result = pytester.runpytest(
+        "top/aaa/pkg", "top/aaa", "--collect-only", "--keep-duplicates"
+    )
+    result.stdout.fnmatch_lines(
+        [
+            "<Dir test_collection_hierarchy*>",
+            "  <Dir top>",
+            "    <Dir aaa>",
+            "      <Package pkg>",
+            "        <Module test_pkg.py>",
+            "          <Function test_it>",
+            "          <Function test_it>",
+            "      <Module test_aaa.py>",
+            "        <Function test_it>",
+        ],
+        consecutive=True,
+    )
diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py
index f29ca1dfa59..32453739e8c 100644
--- a/testing/python/fixtures.py
+++ b/testing/python/fixtures.py
@@ -1,17 +1,20 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import os
+from pathlib import Path
 import sys
 import textwrap
-from pathlib import Path
 
-import pytest
-from _pytest import fixtures
 from _pytest.compat import getfuncargnames
 from _pytest.config import ExitCode
-from _pytest.fixtures import FixtureRequest
+from _pytest.fixtures import deduplicate_names
+from _pytest.fixtures import TopRequest
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import get_public_names
 from _pytest.pytester import Pytester
 from _pytest.python import Function
+import pytest
 
 
 def test_getfuncargnames_functions():
@@ -73,6 +76,16 @@ class B(A):
     assert getfuncargnames(B.static, cls=B) == ("arg1", "arg2")
 
 
+@pytest.mark.skipif(
+    sys.version_info >= (3, 13),
+    reason="""\
+In python 3.13, this will raise FutureWarning:
+functools.partial will be a method descriptor in future Python versions;
+wrap it in staticmethod() if you want to preserve the old behavior
+
+But the wrapped 'functools.partial' is tested by 'test_getfuncargnames_staticmethod_partial' below.
+""",
+)
 def test_getfuncargnames_partial():
     """Check getfuncargnames for methods defined with functools.partial (#5701)"""
     import functools
@@ -103,10 +116,6 @@ class T:
 
 @pytest.mark.pytester_example_path("fixtures/fill_fixtures")
 class TestFillFixtures:
-    def test_fillfuncargs_exposed(self):
-        # used by oejskit, kept for compatibility
-        assert pytest._fillfuncargs == fixtures._fillfuncargs
-
     def test_funcarg_lookupfails(self, pytester: Pytester) -> None:
         pytester.copy_example()
         result = pytester.runpytest()  # "--collect-only")
@@ -291,7 +300,8 @@ def spam(request):
                 def spam():
                     return 'spam'
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         testfile = subdir.joinpath("test_spam.py")
         testfile.write_text(
@@ -300,7 +310,8 @@ def spam():
                 def test_spam(spam):
                     assert spam == "spam"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(["*1 passed*"])
@@ -363,7 +374,8 @@ def spam():
                 def spam(request):
                     return request.param
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         testfile = subdir.joinpath("test_spam.py")
         testfile.write_text(
@@ -375,7 +387,8 @@ def test_spam(spam):
                     assert spam == params['spam']
                     params['spam'] += 1
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(["*3 passed*"])
@@ -407,7 +420,8 @@ def spam():
                 def spam(request):
                     return request.param
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         testfile = subdir.joinpath("test_spam.py")
         testfile.write_text(
@@ -419,7 +433,8 @@ def test_spam(spam):
                     assert spam == params['spam']
                     params['spam'] += 1
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(["*3 passed*"])
@@ -657,7 +672,7 @@ def test_func(something): pass
         """
         )
         assert isinstance(item, Function)
-        req = fixtures.FixtureRequest(item, _ispytest=True)
+        req = TopRequest(item, _ispytest=True)
         assert req.function == item.obj
         assert req.keywords == item.keywords
         assert hasattr(req.module, "test_func")
@@ -697,10 +712,9 @@ def test_method(self, something):
         """
         )
         (item1,) = pytester.genitems([modcol])
+        assert isinstance(item1, Function)
         assert item1.name == "test_method"
-        arg2fixturedefs = fixtures.FixtureRequest(
-            item1, _ispytest=True
-        )._arg2fixturedefs
+        arg2fixturedefs = TopRequest(item1, _ispytest=True)._arg2fixturedefs
         assert len(arg2fixturedefs) == 1
         assert arg2fixturedefs["something"][0].argname == "something"
 
@@ -710,7 +724,7 @@ def test_method(self, something):
     )
     def test_request_garbage(self, pytester: Pytester) -> None:
         try:
-            import xdist  # noqa
+            import xdist  # noqa: F401
         except ImportError:
             pass
         else:
@@ -930,8 +944,9 @@ def test_request_subrequest_addfinalizer_exceptions(
         self, pytester: Pytester
     ) -> None:
         """
-        Ensure exceptions raised during teardown by a finalizer are suppressed
-        until all finalizers are called, re-raising the first exception (#2440)
+        Ensure exceptions raised during teardown by finalizers are suppressed
+        until all finalizers are called, then re-raised together in an
+        exception group (#2440)
         """
         pytester.makepyfile(
             """
@@ -958,14 +973,23 @@ def test_second():
         """
         )
         result = pytester.runpytest()
+        result.assert_outcomes(passed=2, errors=1)
         result.stdout.fnmatch_lines(
-            ["*Exception: Error in excepts fixture", "* 2 passed, 1 error in *"]
+            [
+                '  | *ExceptionGroup: errors while tearing down fixture "subrequest" of <Function test_first> (2 sub-exceptions)',  # noqa: E501
+                "  +-+---------------- 1 ----------------",
+                "    | Exception: Error in something fixture",
+                "    +---------------- 2 ----------------",
+                "    | Exception: Error in excepts fixture",
+                "    +------------------------------------",
+            ],
         )
 
     def test_request_getmodulepath(self, pytester: Pytester) -> None:
         modcol = pytester.getmodulecol("def test_somefunc(): pass")
         (item,) = pytester.genitems([modcol])
-        req = fixtures.FixtureRequest(item, _ispytest=True)
+        assert isinstance(item, Function)
+        req = TopRequest(item, _ispytest=True)
         assert req.path == modcol.path
 
     def test_request_fixturenames(self, pytester: Pytester) -> None:
@@ -1041,10 +1065,11 @@ def test_fixtures_sub_subdir_normalize_sep(self, pytester: Pytester) -> None:
                 def arg1():
                     pass
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         p = b.joinpath("test_module.py")
-        p.write_text("def test_func(arg1): pass")
+        p.write_text("def test_func(arg1): pass", encoding="utf-8")
         result = pytester.runpytest(p, "--fixtures")
         assert result.ret == 0
         result.stdout.fnmatch_lines(
@@ -1122,7 +1147,8 @@ def test_func2(self, something):
                     pass
         """
         )
-        req1 = fixtures.FixtureRequest(item1, _ispytest=True)
+        assert isinstance(item1, Function)
+        req1 = TopRequest(item1, _ispytest=True)
         assert "xfail" not in item1.keywords
         req1.applymarker(pytest.mark.xfail)
         assert "xfail" in item1.keywords
@@ -1233,8 +1259,9 @@ def test_add(arg2):
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(
             [
-                "*ScopeMismatch*involved factories*",
+                "*ScopeMismatch*Requesting fixture stack*",
                 "test_receives_funcargs_scope_mismatch.py:6:  def arg2(arg1)",
+                "Requested fixture:",
                 "test_receives_funcargs_scope_mismatch.py:2:  def arg1()",
                 "*1 error*",
             ]
@@ -1260,7 +1287,13 @@ def test_add(arg1, arg2):
         )
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(
-            ["*ScopeMismatch*involved factories*", "* def arg2*", "*1 error*"]
+            [
+                "*ScopeMismatch*Requesting fixture stack*",
+                "* def arg2(arg1)",
+                "Requested fixture:",
+                "* def arg1()",
+                "*1 error*",
+            ],
         )
 
     def test_invalid_scope(self, pytester: Pytester) -> None:
@@ -1283,7 +1316,7 @@ def test_nothing(badscope):
     @pytest.mark.parametrize("scope", ["function", "session"])
     def test_parameters_without_eq_semantics(self, scope, pytester: Pytester) -> None:
         pytester.makepyfile(
-            """
+            f"""
             class NoEq1:  # fails on `a == b` statement
                 def __eq__(self, _):
                     raise RuntimeError
@@ -1305,9 +1338,7 @@ def test1(no_eq):
 
             def test2(no_eq):
                 pass
-        """.format(
-                scope=scope
-            )
+        """
         )
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(["*4 passed*"])
@@ -1404,6 +1435,23 @@ def test_two(self):
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=2)
 
+    def test_empty_usefixtures_marker(self, pytester: Pytester) -> None:
+        """Empty usefixtures() marker issues a warning (#12439)."""
+        pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.usefixtures()
+            def test_one():
+                assert 1 == 1
+        """
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(
+            "*PytestWarning: usefixtures() in test_empty_usefixtures_marker.py::test_one"
+            " without arguments has no effect"
+        )
+
     def test_usefixtures_ini(self, pytester: Pytester) -> None:
         pytester.makeini(
             """
@@ -1526,6 +1574,95 @@ def test_printer_2(self):
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(["* 2 passed in *"])
 
+    def test_parameterized_fixture_caching(self, pytester: Pytester) -> None:
+        """Regression test for #12600."""
+        pytester.makepyfile(
+            """
+            import pytest
+            from itertools import count
+
+            CACHE_MISSES = count(0)
+
+            def pytest_generate_tests(metafunc):
+                if "my_fixture" in metafunc.fixturenames:
+                    # Use unique objects for parametrization (as opposed to small strings
+                    # and small integers which are singletons).
+                    metafunc.parametrize("my_fixture", [[1], [2]], indirect=True)
+
+            @pytest.fixture(scope='session')
+            def my_fixture(request):
+                next(CACHE_MISSES)
+
+            def test1(my_fixture):
+                pass
+
+            def test2(my_fixture):
+                pass
+
+            def teardown_module():
+                assert next(CACHE_MISSES) == 2
+            """
+        )
+        result = pytester.runpytest()
+        result.stdout.no_fnmatch_line("* ERROR at teardown *")
+
+    def test_unwrapping_pytest_fixture(self, pytester: Pytester) -> None:
+        """Ensure the unwrap method on `FixtureFunctionDefinition` correctly wraps and unwraps methods and functions"""
+        pytester.makepyfile(
+            """
+            import pytest
+            import inspect
+
+            class FixtureFunctionDefTestClass:
+                def __init__(self) -> None:
+                    self.i = 10
+
+                @pytest.fixture
+                def fixture_function_def_test_method(self):
+                    return self.i
+
+
+            @pytest.fixture
+            def fixture_function_def_test_func():
+                return 9
+
+
+            def test_get_wrapped_func_returns_method():
+                obj = FixtureFunctionDefTestClass()
+                wrapped_function_result = (
+                    obj.fixture_function_def_test_method._get_wrapped_function()
+                )
+                assert inspect.ismethod(wrapped_function_result)
+                assert wrapped_function_result() == 10
+
+
+            def test_get_wrapped_func_returns_function():
+                assert fixture_function_def_test_func._get_wrapped_function()() == 9
+            """
+        )
+        result = pytester.runpytest()
+        result.assert_outcomes(passed=2)
+
+    def test_fixture_wrapped_looks_liked_wrapped_function(
+        self, pytester: Pytester
+    ) -> None:
+        """Ensure that `FixtureFunctionDefinition` behaves like the function it wrapped."""
+        pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.fixture
+            def fixture_function_def_test_func():
+                return 9
+            fixture_function_def_test_func.__doc__ = "documentation"
+
+            def test_fixture_has_same_doc():
+                assert fixture_function_def_test_func.__doc__ == "documentation"
+            """
+        )
+        result = pytester.runpytest()
+        result.assert_outcomes(passed=1)
+
 
 class TestFixtureManagerParseFactories:
     @pytest.fixture
@@ -1570,7 +1707,7 @@ def test_parsefactories_conftest(self, pytester: Pytester) -> None:
             """
             def test_hello(item, fm):
                 for name in ("fm", "hello", "item"):
-                    faclist = fm.getfixturedefs(name, item.nodeid)
+                    faclist = fm.getfixturedefs(name, item)
                     assert len(faclist) == 1
                     fac = faclist[0]
                     assert fac.func.__name__ == name
@@ -1594,7 +1731,7 @@ class TestClass(object):
                 def hello(self, request):
                     return "class"
                 def test_hello(self, item, fm):
-                    faclist = fm.getfixturedefs("hello", item.nodeid)
+                    faclist = fm.getfixturedefs("hello", item)
                     print(faclist)
                     assert len(faclist) == 3
 
@@ -1621,7 +1758,8 @@ def test_parsefactories_relative_node_ids(
             def one():
                 return 1
             """
-            )
+            ),
+            encoding="utf-8",
         )
         package.joinpath("test_x.py").write_text(
             textwrap.dedent(
@@ -1629,7 +1767,8 @@ def one():
                 def test_x(one):
                     assert one == 1
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         sub = package.joinpath("sub")
         sub.mkdir()
@@ -1642,7 +1781,8 @@ def test_x(one):
                 def one():
                     return 2
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         sub.joinpath("test_y.py").write_text(
             textwrap.dedent(
@@ -1650,7 +1790,8 @@ def one():
                 def test_x(one):
                     assert one == 2
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=2)
@@ -1675,7 +1816,8 @@ def setup_module():
                 def teardown_module():
                     values[:] = []
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         package.joinpath("test_x.py").write_text(
             textwrap.dedent(
@@ -1684,7 +1826,8 @@ def teardown_module():
                 def test_x():
                     assert values == ["package"]
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         package = pytester.mkdir("package2")
         package.joinpath("__init__.py").write_text(
@@ -1696,7 +1839,8 @@ def setup_module():
                 def teardown_module():
                     values[:] = []
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         package.joinpath("test_x.py").write_text(
             textwrap.dedent(
@@ -1705,7 +1849,8 @@ def teardown_module():
                 def test_x():
                     assert values == ["package2"]
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=2)
@@ -1718,7 +1863,7 @@ def test_package_fixture_complex(self, pytester: Pytester) -> None:
         )
         pytester.syspathinsert(pytester.path.name)
         package = pytester.mkdir("package")
-        package.joinpath("__init__.py").write_text("")
+        package.joinpath("__init__.py").write_text("", encoding="utf-8")
         package.joinpath("conftest.py").write_text(
             textwrap.dedent(
                 """\
@@ -1735,7 +1880,8 @@ def two():
                     yield values
                     values.pop()
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         package.joinpath("test_x.py").write_text(
             textwrap.dedent(
@@ -1746,7 +1892,8 @@ def test_package_autouse():
                 def test_package(one):
                     assert values == ["package-auto", "package"]
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=2)
@@ -1790,7 +1937,7 @@ def test_parsefactories_conftest(self, pytester: Pytester) -> None:
             """
             from _pytest.pytester import get_public_names
             def test_check_setup(item, fm):
-                autousenames = list(fm._getautousenames(item.nodeid))
+                autousenames = list(fm._getautousenames(item))
                 assert len(get_public_names(autousenames)) == 2
                 assert "perfunction2" in autousenames
                 assert "perfunction" in autousenames
@@ -1896,8 +2043,12 @@ def hello():
         """
         )
         conftest.rename(a.joinpath(conftest.name))
-        a.joinpath("test_something.py").write_text("def test_func(): pass")
-        b.joinpath("test_otherthing.py").write_text("def test_func(): pass")
+        a.joinpath("test_something.py").write_text(
+            "def test_func(): pass", encoding="utf-8"
+        )
+        b.joinpath("test_otherthing.py").write_text(
+            "def test_func(): pass", encoding="utf-8"
+        )
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(
             """
@@ -1943,7 +2094,8 @@ def app():
                     import sys
                     sys._myapp = "hello"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         sub = pkgdir.joinpath("tests")
         sub.mkdir()
@@ -1956,7 +2108,8 @@ def app():
                 def test_app():
                     assert sys._myapp == "hello"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         reprec = pytester.inline_run("-s")
         reprec.assertoutcome(passed=1)
@@ -2081,9 +2234,7 @@ def test_2(self):
         reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
         reprec.assertoutcome(passed=8)
         config = reprec.getcalls("pytest_unconfigure")[0].config
-        values = config.pluginmanager._getconftestmodules(
-            p, importmode="prepend", rootpath=pytester.path
-        )[0].values
+        values = config.pluginmanager._getconftestmodules(p)[0].values
         assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
 
     def test_scope_ordering(self, pytester: Pytester) -> None:
@@ -2164,14 +2315,14 @@ def test_ordering_dependencies_torndown_first(
     ) -> None:
         """#226"""
         pytester.makepyfile(
-            """
+            f"""
             import pytest
             values = []
-            @pytest.fixture(%(param1)s)
+            @pytest.fixture({param1})
             def arg1(request):
                 request.addfinalizer(lambda: values.append("fin1"))
                 values.append("new1")
-            @pytest.fixture(%(param2)s)
+            @pytest.fixture({param2})
             def arg2(request, arg1):
                 request.addfinalizer(lambda: values.append("fin2"))
                 values.append("new2")
@@ -2181,11 +2332,29 @@ def test_arg(arg2):
             def test_check():
                 assert values == ["new1", "new2", "fin2", "fin1"]
         """
-            % locals()
         )
         reprec = pytester.inline_run("-s")
         reprec.assertoutcome(passed=2)
 
+    def test_reordering_catastrophic_performance(self, pytester: Pytester) -> None:
+        """Check that a certain high-scope parametrization pattern doesn't cause
+        a catasrophic slowdown.
+
+        Regression test for #12355.
+        """
+        pytester.makepyfile("""
+            import pytest
+
+            params = tuple("abcdefghijklmnopqrstuvwxyz")
+            @pytest.mark.parametrize(params, [range(len(params))] * 3, scope="module")
+            def test_parametrize(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z):
+                pass
+        """)
+
+        result = pytester.runpytest()
+
+        result.assert_outcomes(passed=3)
+
 
 class TestFixtureMarker:
     def test_parametrize(self, pytester: Pytester) -> None:
@@ -2235,18 +2404,17 @@ def test_override_parametrized_fixture_issue_979(
         This was a regression introduced in the fix for #736.
         """
         pytester.makepyfile(
-            """
+            f"""
             import pytest
 
             @pytest.fixture(params=[1, 2])
             def fixt(request):
                 return request.param
 
-            @pytest.mark.parametrize(%s, [(3, 'x'), (4, 'x')])
+            @pytest.mark.parametrize({param_args}, [(3, 'x'), (4, 'x')])
             def test_foo(fixt, val):
                 pass
         """
-            % param_args
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=2)
@@ -2442,6 +2610,33 @@ def test_1(arg):
             ["*ScopeMismatch*You tried*function*session*request*"]
         )
 
+    def test_scope_mismatch_already_computed_dynamic(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            test_it="""
+                import pytest
+
+                @pytest.fixture(scope="function")
+                def fixfunc(): pass
+
+                @pytest.fixture(scope="module")
+                def fixmod(fixfunc): pass
+
+                def test_it(request, fixfunc):
+                    request.getfixturevalue("fixmod")
+            """,
+        )
+
+        result = pytester.runpytest()
+        assert result.ret == ExitCode.TESTS_FAILED
+        result.stdout.fnmatch_lines(
+            [
+                "*ScopeMismatch*Requesting fixture stack*",
+                "test_it.py:6:  def fixmod(fixfunc)",
+                "Requested fixture:",
+                "test_it.py:3:  def fixfunc()",
+            ]
+        )
+
     def test_dynamic_scope(self, pytester: Pytester) -> None:
         pytester.makeconftest(
             """
@@ -2688,12 +2883,12 @@ def test2(reprovision):
             """
             test_dynamic_parametrized_ordering.py::test[flavor1-vxlan] PASSED
             test_dynamic_parametrized_ordering.py::test2[flavor1-vxlan] PASSED
-            test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED
-            test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED
-            test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED
-            test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED
             test_dynamic_parametrized_ordering.py::test[flavor1-vlan] PASSED
             test_dynamic_parametrized_ordering.py::test2[flavor1-vlan] PASSED
+            test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED
+            test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED
+            test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED
+            test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED
         """
         )
 
@@ -2874,7 +3069,7 @@ def test_finish():
             *3 passed*
         """
         )
-        result.stdout.no_fnmatch_line("*error*")
+        assert result.ret == 0
 
     def test_fixture_finalizer(self, pytester: Pytester) -> None:
         pytester.makeconftest(
@@ -2886,7 +3081,7 @@ def test_fixture_finalizer(self, pytester: Pytester) -> None:
             def browser(request):
 
                 def finalize():
-                    sys.stdout.write_text('Finalized')
+                    sys.stdout.write_text('Finalized', encoding='utf-8')
                 request.addfinalizer(finalize)
                 return {}
         """
@@ -2904,7 +3099,8 @@ def browser(browser):
                 def test_browser(browser):
                     assert browser['visited'] is True
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         reprec = pytester.runpytest("-s")
         for test in ["test_browser"]:
@@ -3015,21 +3211,21 @@ def test_finalizer_order_on_parametrization(
     ) -> None:
         """#246"""
         pytester.makepyfile(
-            """
+            f"""
             import pytest
             values = []
 
-            @pytest.fixture(scope=%(scope)r, params=["1"])
+            @pytest.fixture(scope={scope!r}, params=["1"])
             def fix1(request):
                 return request.param
 
-            @pytest.fixture(scope=%(scope)r)
+            @pytest.fixture(scope={scope!r})
             def fix2(request, base):
                 def cleanup_fix2():
                     assert not values, "base should not have been finalized"
                 request.addfinalizer(cleanup_fix2)
 
-            @pytest.fixture(scope=%(scope)r)
+            @pytest.fixture(scope={scope!r})
             def base(request, fix1):
                 def cleanup_base():
                     values.append("fin_base")
@@ -3043,7 +3239,6 @@ def test_baz(base, fix2):
             def test_other():
                 pass
         """
-            % {"scope": scope}
         )
         reprec = pytester.inline_run("-lvs")
         reprec.assertoutcome(passed=3)
@@ -3229,13 +3424,13 @@ class TestRequestScopeAccess:
 
     def test_setup(self, pytester: Pytester, scope, ok, error) -> None:
         pytester.makepyfile(
-            """
+            f"""
             import pytest
-            @pytest.fixture(scope=%r, autouse=True)
+            @pytest.fixture(scope={scope!r}, autouse=True)
             def myscoped(request):
-                for x in %r:
+                for x in {ok.split()}:
                     assert hasattr(request, x)
-                for x in %r:
+                for x in {error.split()}:
                     pytest.raises(AttributeError, lambda:
                         getattr(request, x))
                 assert request.session
@@ -3243,20 +3438,19 @@ def myscoped(request):
             def test_func():
                 pass
         """
-            % (scope, ok.split(), error.split())
         )
         reprec = pytester.inline_run("-l")
         reprec.assertoutcome(passed=1)
 
     def test_funcarg(self, pytester: Pytester, scope, ok, error) -> None:
         pytester.makepyfile(
-            """
+            f"""
             import pytest
-            @pytest.fixture(scope=%r)
+            @pytest.fixture(scope={scope!r})
             def arg(request):
-                for x in %r:
+                for x in {ok.split()!r}:
                     assert hasattr(request, x)
-                for x in %r:
+                for x in {error.split()!r}:
                     pytest.raises(AttributeError, lambda:
                         getattr(request, x))
                 assert request.session
@@ -3264,7 +3458,6 @@ def arg(request):
             def test_func(arg):
                 pass
         """
-            % (scope, ok.split(), error.split())
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=1)
@@ -3336,12 +3529,38 @@ def test_something():
             ["*def gen(qwe123):*", "*fixture*qwe123*not found*", "*1 error*"]
         )
 
+    def test_cached_exception_doesnt_get_longer(self, pytester: Pytester) -> None:
+        """Regression test for #12204."""
+        pytester.makepyfile(
+            """
+            import pytest
+            @pytest.fixture(scope="session")
+            def bad(): 1 / 0
+
+            def test_1(bad): pass
+            def test_2(bad): pass
+            def test_3(bad): pass
+            """
+        )
+
+        result = pytester.runpytest_inprocess("--tb=native")
+        assert result.ret == ExitCode.TESTS_FAILED
+        failures = result.reprec.getfailures()  # type: ignore[attr-defined]
+        assert len(failures) == 3
+        lines1 = failures[1].longrepr.reprtraceback.reprentries[0].lines
+        lines2 = failures[2].longrepr.reprtraceback.reprentries[0].lines
+        assert len(lines1) == len(lines2)
+
 
 class TestShowFixtures:
     def test_funcarg_compat(self, pytester: Pytester) -> None:
         config = pytester.parseconfigure("--funcargs")
         assert config.option.showfixtures
 
+    def test_show_help(self, pytester: Pytester) -> None:
+        result = pytester.runpytest("--fixtures", "--help")
+        assert not result.ret
+
     def test_show_fixtures(self, pytester: Pytester) -> None:
         result = pytester.runpytest("--fixtures")
         result.stdout.fnmatch_lines(
@@ -3855,7 +4074,8 @@ def test_non_relative_path(self, pytester: Pytester) -> None:
                 def fix_with_param(request):
                     return request.param
                 """
-            )
+            ),
+            encoding="utf-8",
         )
 
         testfile = tests_dir.joinpath("test_foos.py")
@@ -3867,7 +4087,8 @@ def fix_with_param(request):
                 def test_foo(request):
                     request.getfixturevalue('fix_with_param')
                 """
-            )
+            ),
+            encoding="utf-8",
         )
 
         os.chdir(tests_dir)
@@ -3983,7 +4204,8 @@ def test_func(m1):
         """
         )
         items, _ = pytester.inline_genitems()
-        request = FixtureRequest(items[0], _ispytest=True)
+        assert isinstance(items[0], Function)
+        request = TopRequest(items[0], _ispytest=True)
         assert request.fixturenames == "m1 f1".split()
 
     def test_func_closure_with_native_fixtures(
@@ -4031,7 +4253,8 @@ def test_foo(f1, p1, m1, f2, s1): pass
         """
         )
         items, _ = pytester.inline_genitems()
-        request = FixtureRequest(items[0], _ispytest=True)
+        assert isinstance(items[0], Function)
+        request = TopRequest(items[0], _ispytest=True)
         # order of fixtures based on their scope and position in the parameter list
         assert (
             request.fixturenames
@@ -4058,7 +4281,8 @@ def test_func(f1, m1):
         """
         )
         items, _ = pytester.inline_genitems()
-        request = FixtureRequest(items[0], _ispytest=True)
+        assert isinstance(items[0], Function)
+        request = TopRequest(items[0], _ispytest=True)
         assert request.fixturenames == "m1 f1".split()
 
     def test_func_closure_scopes_reordered(self, pytester: Pytester) -> None:
@@ -4091,7 +4315,8 @@ def test_func(self, f2, f1, c1, m1, s1):
         """
         )
         items, _ = pytester.inline_genitems()
-        request = FixtureRequest(items[0], _ispytest=True)
+        assert isinstance(items[0], Function)
+        request = TopRequest(items[0], _ispytest=True)
         assert request.fixturenames == "s1 m1 c1 f2 f1".split()
 
     def test_func_closure_same_scope_closer_root_first(
@@ -4133,7 +4358,8 @@ def test_func(m_test, f1):
             }
         )
         items, _ = pytester.inline_genitems()
-        request = FixtureRequest(items[0], _ispytest=True)
+        assert isinstance(items[0], Function)
+        request = TopRequest(items[0], _ispytest=True)
         assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split()
 
     def test_func_closure_all_scopes_complex(self, pytester: Pytester) -> None:
@@ -4177,9 +4403,43 @@ def test_func(self, f2, f1, m2):
         """
         )
         items, _ = pytester.inline_genitems()
-        request = FixtureRequest(items[0], _ispytest=True)
+        assert isinstance(items[0], Function)
+        request = TopRequest(items[0], _ispytest=True)
         assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split()
 
+    def test_parametrized_package_scope_reordering(self, pytester: Pytester) -> None:
+        """A parameterized package-scoped fixture correctly reorders items to
+        minimize setups & teardowns.
+
+        Regression test for #12328.
+        """
+        pytester.makepyfile(
+            __init__="",
+            conftest="""
+                import pytest
+                @pytest.fixture(scope="package", params=["a", "b"])
+                def fix(request):
+                    return request.param
+            """,
+            test_1="def test1(fix): pass",
+            test_2="def test2(fix): pass",
+        )
+
+        result = pytester.runpytest("--setup-plan")
+        assert result.ret == ExitCode.OK
+        result.stdout.fnmatch_lines(
+            [
+                "  SETUP    P fix['a']",
+                "        test_1.py::test1[a] (fixtures used: fix, request)",
+                "        test_2.py::test2[a] (fixtures used: fix, request)",
+                "  TEARDOWN P fix['a']",
+                "  SETUP    P fix['b']",
+                "        test_1.py::test1[b] (fixtures used: fix, request)",
+                "        test_2.py::test2[b] (fixtures used: fix, request)",
+                "  TEARDOWN P fix['b']",
+            ],
+        )
+
     def test_multiple_packages(self, pytester: Pytester) -> None:
         """Complex test involving multiple package fixtures. Make sure teardowns
         are executed in order.
@@ -4196,7 +4456,7 @@ def test_multiple_packages(self, pytester: Pytester) -> None:
                 └── test_2.py
         """
         root = pytester.mkdir("root")
-        root.joinpath("__init__.py").write_text("values = []")
+        root.joinpath("__init__.py").write_text("values = []", encoding="utf-8")
         sub1 = root.joinpath("sub1")
         sub1.mkdir()
         sub1.joinpath("__init__.py").touch()
@@ -4211,7 +4471,8 @@ def fix():
                 yield values
                 assert values.pop() == "pre-sub1"
         """
-            )
+            ),
+            encoding="utf-8",
         )
         sub1.joinpath("test_1.py").write_text(
             textwrap.dedent(
@@ -4220,7 +4481,8 @@ def fix():
             def test_1(fix):
                 assert values == ["pre-sub1"]
         """
-            )
+            ),
+            encoding="utf-8",
         )
         sub2 = root.joinpath("sub2")
         sub2.mkdir()
@@ -4236,7 +4498,8 @@ def fix():
                 yield values
                 assert values.pop() == "pre-sub2"
         """
-            )
+            ),
+            encoding="utf-8",
         )
         sub2.joinpath("test_2.py").write_text(
             textwrap.dedent(
@@ -4245,7 +4508,8 @@ def fix():
             def test_2(fix):
                 assert values == ["pre-sub2"]
         """
-            )
+            ),
+            encoding="utf-8",
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=2)
@@ -4294,6 +4558,42 @@ def fix():
         assert fix() == 1
 
 
+def test_fixture_double_decorator(pytester: Pytester) -> None:
+    """Check if an error is raised when using @pytest.fixture twice."""
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture
+        @pytest.fixture
+        def fixt():
+            pass
+        """
+    )
+    result = pytester.runpytest()
+    result.assert_outcomes(errors=1)
+    result.stdout.fnmatch_lines(
+        [
+            "E * ValueError: @pytest.fixture is being applied more than once to the same function 'fixt'"
+        ]
+    )
+
+
+def test_fixture_class(pytester: Pytester) -> None:
+    """Check if an error is raised when using @pytest.fixture on a class."""
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture
+        class A:
+            pass
+        """
+    )
+    result = pytester.runpytest()
+    result.assert_outcomes(errors=1)
+
+
 def test_fixture_param_shadowing(pytester: Pytester) -> None:
     """Parametrized arguments would be shadowed if a fixture with the same name also exists (#5036)"""
     pytester.makepyfile(
@@ -4343,7 +4643,7 @@ def test_fixture_named_request(pytester: Pytester) -> None:
     result.stdout.fnmatch_lines(
         [
             "*'request' is a reserved word for fixtures, use another name:",
-            "  *test_fixture_named_request.py:5",
+            "  *test_fixture_named_request.py:8",
         ]
     )
 
@@ -4472,3 +4772,264 @@ def test_fixt(custom):
     result.assert_outcomes(errors=1)
     result.stdout.fnmatch_lines([expected])
     assert result.ret == ExitCode.TESTS_FAILED
+
+
+def test_deduplicate_names() -> None:
+    items = deduplicate_names("abacd")
+    assert items == ("a", "b", "c", "d")
+    items = deduplicate_names((*items, "g", "f", "g", "e", "b"))
+    assert items == ("a", "b", "c", "d", "g", "f", "e")
+
+
+def test_staticmethod_classmethod_fixture_instance(pytester: Pytester) -> None:
+    """Ensure that static and class methods get and have access to a fresh
+    instance.
+
+    This also ensures `setup_method` works well with static and class methods.
+
+    Regression test for #12065.
+    """
+    pytester.makepyfile(
+        """
+        import pytest
+
+        class Test:
+            ran_setup_method = False
+            ran_fixture = False
+
+            def setup_method(self):
+                assert not self.ran_setup_method
+                self.ran_setup_method = True
+
+            @pytest.fixture(autouse=True)
+            def fixture(self):
+                assert not self.ran_fixture
+                self.ran_fixture = True
+
+            def test_method(self):
+                assert self.ran_setup_method
+                assert self.ran_fixture
+
+            @staticmethod
+            def test_1(request):
+                assert request.instance.ran_setup_method
+                assert request.instance.ran_fixture
+
+            @classmethod
+            def test_2(cls, request):
+                assert request.instance.ran_setup_method
+                assert request.instance.ran_fixture
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == ExitCode.OK
+    result.assert_outcomes(passed=3)
+
+
+def test_scoped_fixture_caching(pytester: Pytester) -> None:
+    """Make sure setup and finalization is only run once when using scoped fixture
+    multiple times."""
+    pytester.makepyfile(
+        """
+        from __future__ import annotations
+
+        from typing import Generator
+
+        import pytest
+        executed: list[str] = []
+        @pytest.fixture(scope="class")
+        def fixture_1() -> Generator[None, None, None]:
+            executed.append("fix setup")
+            yield
+            executed.append("fix teardown")
+
+
+        class TestFixtureCaching:
+            def test_1(self, fixture_1: None) -> None:
+                assert executed == ["fix setup"]
+
+            def test_2(self, fixture_1: None) -> None:
+                assert executed == ["fix setup"]
+
+
+        def test_expected_setup_and_teardown() -> None:
+            assert executed == ["fix setup", "fix teardown"]
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == 0
+
+
+def test_scoped_fixture_caching_exception(pytester: Pytester) -> None:
+    """Make sure setup & finalization is only run once for scoped fixture, with a cached exception."""
+    pytester.makepyfile(
+        """
+        from __future__ import annotations
+
+        import pytest
+        executed_crash: list[str] = []
+
+
+        @pytest.fixture(scope="class")
+        def fixture_crash(request: pytest.FixtureRequest) -> None:
+            executed_crash.append("fix_crash setup")
+
+            def my_finalizer() -> None:
+                executed_crash.append("fix_crash teardown")
+
+            request.addfinalizer(my_finalizer)
+
+            raise Exception("foo")
+
+
+        class TestFixtureCachingException:
+            @pytest.mark.xfail
+            def test_crash_1(self, fixture_crash: None) -> None:
+                ...
+
+            @pytest.mark.xfail
+            def test_crash_2(self, fixture_crash: None) -> None:
+                ...
+
+
+        def test_crash_expected_setup_and_teardown() -> None:
+            assert executed_crash == ["fix_crash setup", "fix_crash teardown"]
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == 0
+
+
+def test_scoped_fixture_teardown_order(pytester: Pytester) -> None:
+    """
+    Make sure teardowns happen in reverse order of setup with scoped fixtures, when
+    a later test only depends on a subset of scoped fixtures.
+
+    Regression test for https://github.com/pytest-dev/pytest/issues/1489
+    """
+    pytester.makepyfile(
+        """
+        from typing import Generator
+
+        import pytest
+
+
+        last_executed = ""
+
+
+        @pytest.fixture(scope="module")
+        def fixture_1() -> Generator[None, None, None]:
+            global last_executed
+            assert last_executed == ""
+            last_executed = "fixture_1_setup"
+            yield
+            assert last_executed == "fixture_2_teardown"
+            last_executed = "fixture_1_teardown"
+
+
+        @pytest.fixture(scope="module")
+        def fixture_2() -> Generator[None, None, None]:
+            global last_executed
+            assert last_executed == "fixture_1_setup"
+            last_executed = "fixture_2_setup"
+            yield
+            assert last_executed == "run_test"
+            last_executed = "fixture_2_teardown"
+
+
+        def test_fixture_teardown_order(fixture_1: None, fixture_2: None) -> None:
+            global last_executed
+            assert last_executed == "fixture_2_setup"
+            last_executed = "run_test"
+
+
+        def test_2(fixture_1: None) -> None:
+            # This would previously queue an additional teardown of fixture_1,
+            # despite fixture_1's value being cached, which caused fixture_1 to be
+            # torn down before fixture_2 - violating the rule that teardowns should
+            # happen in reverse order of setup.
+            pass
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == 0
+
+
+def test_subfixture_teardown_order(pytester: Pytester) -> None:
+    """
+    Make sure fixtures don't re-register their finalization in parent fixtures multiple
+    times, causing ordering failure in their teardowns.
+
+    Regression test for #12135
+    """
+    pytester.makepyfile(
+        """
+        import pytest
+
+        execution_order = []
+
+        @pytest.fixture(scope="class")
+        def fixture_1():
+            ...
+
+        @pytest.fixture(scope="class")
+        def fixture_2(fixture_1):
+            execution_order.append("setup 2")
+            yield
+            execution_order.append("teardown 2")
+
+        @pytest.fixture(scope="class")
+        def fixture_3(fixture_1):
+            execution_order.append("setup 3")
+            yield
+            execution_order.append("teardown 3")
+
+        class TestFoo:
+            def test_initialize_fixtures(self, fixture_2, fixture_3):
+                ...
+
+            # This would previously reschedule fixture_2's finalizer in the parent fixture,
+            # causing it to be torn down before fixture 3.
+            def test_reschedule_fixture_2(self, fixture_2):
+                ...
+
+            # Force finalization directly on fixture_1
+            # Otherwise the cleanup would sequence 3&2 before 1 as normal.
+            @pytest.mark.parametrize("fixture_1", [None], indirect=["fixture_1"])
+            def test_finalize_fixture_1(self, fixture_1):
+                ...
+
+        def test_result():
+            assert execution_order == ["setup 2", "setup 3", "teardown 3", "teardown 2"]
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == 0
+
+
+def test_parametrized_fixture_scope_allowed(pytester: Pytester) -> None:
+    """
+    Make sure scope from parametrize does not affect fixture's ability to be
+    depended upon.
+
+    Regression test for #13248
+    """
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture(scope="session")
+        def my_fixture(request):
+            return getattr(request, "param", None)
+
+        @pytest.fixture(scope="session")
+        def another_fixture(my_fixture):
+            return my_fixture
+
+        @pytest.mark.parametrize("my_fixture", ["a value"], indirect=True, scope="function")
+        def test_foo(another_fixture):
+            assert another_fixture == "a value"
+        """
+    )
+    result = pytester.runpytest()
+    result.assert_outcomes(passed=1)
diff --git a/testing/python/integration.py b/testing/python/integration.py
index d138b726638..c96b6e4c260 100644
--- a/testing/python/integration.py
+++ b/testing/python/integration.py
@@ -1,82 +1,11 @@
-from typing import Any
+# mypy: allow-untyped-defs
+from __future__ import annotations
 
-import pytest
-from _pytest import runner
 from _pytest._code import getfslineno
 from _pytest.fixtures import getfixturemarker
 from _pytest.pytester import Pytester
 from _pytest.python import Function
-
-
-class TestOEJSKITSpecials:
-    def test_funcarg_non_pycollectobj(
-        self, pytester: Pytester, recwarn
-    ) -> None:  # rough jstests usage
-        pytester.makeconftest(
-            """
-            import pytest
-            def pytest_pycollect_makeitem(collector, name, obj):
-                if name == "MyClass":
-                    return MyCollector.from_parent(collector, name=name)
-            class MyCollector(pytest.Collector):
-                def reportinfo(self):
-                    return self.path, 3, "xyz"
-        """
-        )
-        modcol = pytester.getmodulecol(
-            """
-            import pytest
-            @pytest.fixture
-            def arg1(request):
-                return 42
-            class MyClass(object):
-                pass
-        """
-        )
-        # this hook finds funcarg factories
-        rep = runner.collect_one_node(collector=modcol)
-        # TODO: Don't treat as Any.
-        clscol: Any = rep.result[0]
-        clscol.obj = lambda arg1: None
-        clscol.funcargs = {}
-        pytest._fillfuncargs(clscol)
-        assert clscol.funcargs["arg1"] == 42
-
-    def test_autouse_fixture(
-        self, pytester: Pytester, recwarn
-    ) -> None:  # rough jstests usage
-        pytester.makeconftest(
-            """
-            import pytest
-            def pytest_pycollect_makeitem(collector, name, obj):
-                if name == "MyClass":
-                    return MyCollector.from_parent(collector, name=name)
-            class MyCollector(pytest.Collector):
-                def reportinfo(self):
-                    return self.path, 3, "xyz"
-        """
-        )
-        modcol = pytester.getmodulecol(
-            """
-            import pytest
-            @pytest.fixture(autouse=True)
-            def hello():
-                pass
-            @pytest.fixture
-            def arg1(request):
-                return 42
-            class MyClass(object):
-                pass
-        """
-        )
-        # this hook finds funcarg factories
-        rep = runner.collect_one_node(modcol)
-        # TODO: Don't treat as Any.
-        clscol: Any = rep.result[0]
-        clscol.obj = lambda: None
-        clscol.funcargs = {}
-        pytest._fillfuncargs(clscol)
-        assert not clscol.funcargs
+import pytest
 
 
 def test_wrapped_getfslineno() -> None:
@@ -116,9 +45,10 @@ def f(x):
         assert values == ("x",)
 
     def test_getfuncargnames_patching(self):
-        from _pytest.compat import getfuncargnames
         from unittest.mock import patch
 
+        from _pytest.compat import getfuncargnames
+
         class T:
             def original(self, x, y, z):
                 pass
@@ -235,7 +165,7 @@ def mock_basename(path):
             @mock.patch("os.path.abspath")
             @mock.patch("os.path.normpath")
             @mock.patch("os.path.basename", new=mock_basename)
-            def test_someting(normpath, abspath, tmp_path):
+            def test_something(normpath, abspath, tmp_path):
                 abspath.return_value = "this"
                 os.path.normpath(os.path.abspath("hello"))
                 normpath.assert_any_call("this")
@@ -248,7 +178,7 @@ def test_someting(normpath, abspath, tmp_path):
         funcnames = [
             call.report.location[2] for call in calls if call.report.when == "call"
         ]
-        assert funcnames == ["T.test_hello", "test_someting"]
+        assert funcnames == ["T.test_hello", "test_something"]
 
     def test_mock_sorting(self, pytester: Pytester) -> None:
         pytest.importorskip("mock", "1.0.1")
@@ -477,27 +407,59 @@ def test_params(a, b):
         res = pytester.runpytest("--collect-only")
         res.stdout.fnmatch_lines(["*spam-2*", "*ham-2*"])
 
+    def test_param_rejects_usefixtures(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize("x", [
+                pytest.param(1, marks=[pytest.mark.usefixtures("foo")]),
+            ])
+            def test_foo(x):
+                pass
+        """
+        )
+        res = pytester.runpytest("--collect-only")
+        res.stdout.fnmatch_lines(
+            ["*test_param_rejects_usefixtures.py:4*", "*pytest.param(*"]
+        )
+
 
 def test_function_instance(pytester: Pytester) -> None:
     items = pytester.getitems(
         """
         def test_func(): pass
+
         class TestIt:
             def test_method(self): pass
+
             @classmethod
             def test_class(cls): pass
+
             @staticmethod
             def test_static(): pass
         """
     )
-    assert len(items) == 3
+    assert len(items) == 4
+
     assert isinstance(items[0], Function)
     assert items[0].name == "test_func"
     assert items[0].instance is None
+
     assert isinstance(items[1], Function)
     assert items[1].name == "test_method"
     assert items[1].instance is not None
     assert items[1].instance.__class__.__name__ == "TestIt"
+
+    # Even class and static methods get an instance!
+    # This is the instance used for bound fixture methods, which
+    # class/staticmethod tests are perfectly able to request.
     assert isinstance(items[2], Function)
-    assert items[2].name == "test_static"
-    assert items[2].instance is None
+    assert items[2].name == "test_class"
+    assert items[2].instance is not None
+
+    assert isinstance(items[3], Function)
+    assert items[3].name == "test_static"
+    assert items[3].instance is not None
+
+    assert items[1].instance is not items[2].instance is not items[3].instance
diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py
index fc0082eb6b9..e8b345aecc6 100644
--- a/testing/python/metafunc.py
+++ b/testing/python/metafunc.py
@@ -1,32 +1,30 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Iterator
+from collections.abc import Sequence
+import dataclasses
 import itertools
 import re
 import sys
 import textwrap
 from typing import Any
 from typing import cast
-from typing import Dict
-from typing import Iterator
-from typing import List
-from typing import Optional
-from typing import Sequence
-from typing import Tuple
-from typing import Union
-
-import attr
+
 import hypothesis
 from hypothesis import strategies
 
-import pytest
 from _pytest import fixtures
 from _pytest import python
-from _pytest.compat import _format_args
 from _pytest.compat import getfuncargnames
 from _pytest.compat import NOTSET
 from _pytest.outcomes import fail
+from _pytest.outcomes import Failed
 from _pytest.pytester import Pytester
-from _pytest.python import _idval
-from _pytest.python import idmaker
+from _pytest.python import Function
+from _pytest.python import IdMaker
 from _pytest.scope import Scope
+import pytest
 
 
 class TestMetafunc:
@@ -35,19 +33,29 @@ def Metafunc(self, func, config=None) -> python.Metafunc:
         # on the funcarg level, so we don't need a full blown
         # initialization.
         class FuncFixtureInfoMock:
-            name2fixturedefs = None
+            name2fixturedefs: dict[str, list[fixtures.FixtureDef[object]]] = {}
 
             def __init__(self, names):
                 self.names_closure = names
 
-        @attr.s
+        @dataclasses.dataclass
+        class FixtureManagerMock:
+            config: Any
+
+        @dataclasses.dataclass
+        class SessionMock:
+            _fixturemanager: FixtureManagerMock
+
+        @dataclasses.dataclass
         class DefinitionMock(python.FunctionDefinition):
-            obj = attr.ib()
-            _nodeid = attr.ib()
+            _nodeid: str
+            obj: object
 
         names = getfuncargnames(func)
         fixtureinfo: Any = FuncFixtureInfoMock(names)
-        definition: Any = DefinitionMock._create(func, "mock::nodeid")
+        definition: Any = DefinitionMock._create(obj=func, _nodeid="mock::nodeid")
+        definition._fixtureinfo = fixtureinfo
+        definition.session = SessionMock(FixtureManagerMock({}))
         return python.Metafunc(definition, fixtureinfo, config, _ispytest=True)
 
     def test_no_funcargs(self) -> None:
@@ -91,7 +99,7 @@ class Exc(Exception):
             def __repr__(self):
                 return "Exc(from_gen)"
 
-        def gen() -> Iterator[Union[int, None, Exc]]:
+        def gen() -> Iterator[int | None | Exc]:
             yield 0
             yield None
             yield Exc()
@@ -99,19 +107,19 @@ def gen() -> Iterator[Union[int, None, Exc]]:
         metafunc = self.Metafunc(func)
         # When the input is an iterator, only len(args) are taken,
         # so the bad Exc isn't reached.
-        metafunc.parametrize("x", [1, 2], ids=gen())  # type: ignore[arg-type]
-        assert [(x.funcargs, x.id) for x in metafunc._calls] == [
+        metafunc.parametrize("x", [1, 2], ids=gen())
+        assert [(x.params, x.id) for x in metafunc._calls] == [
             ({"x": 1}, "0"),
             ({"x": 2}, "2"),
         ]
         with pytest.raises(
             fail.Exception,
             match=(
-                r"In func: ids must be list of string/float/int/bool, found:"
-                r" Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2"
+                r"In func: ids contains unsupported value Exc\(from_gen\) \(type: <class .*Exc'>\) at index 2. "
+                r"Supported types are: .*"
             ),
         ):
-            metafunc.parametrize("x", [1, 2, 3], ids=gen())  # type: ignore[arg-type]
+            metafunc.parametrize("x", [1, 2, 3], ids=gen())
 
     def test_parametrize_bad_scope(self) -> None:
         def func(x):
@@ -141,18 +149,19 @@ def test_find_parametrized_scope(self) -> None:
         """Unit test for _find_parametrized_scope (#3941)."""
         from _pytest.python import _find_parametrized_scope
 
-        @attr.s
+        @dataclasses.dataclass
         class DummyFixtureDef:
-            _scope = attr.ib()
+            _scope: Scope
 
         fixtures_defs = cast(
-            Dict[str, Sequence[fixtures.FixtureDef[object]]],
+            dict[str, Sequence[fixtures.FixtureDef[object]]],
             dict(
                 session_fix=[DummyFixtureDef(Scope.Session)],
                 package_fix=[DummyFixtureDef(Scope.Package)],
                 module_fix=[DummyFixtureDef(Scope.Module)],
                 class_fix=[DummyFixtureDef(Scope.Class)],
                 func_fix=[DummyFixtureDef(Scope.Function)],
+                mixed_fix=[DummyFixtureDef(Scope.Module), DummyFixtureDef(Scope.Class)],
             ),
         )
 
@@ -189,6 +198,7 @@ def find_scope(argnames, indirect):
             )
             == Scope.Module
         )
+        assert find_scope(["mixed_fix"], indirect=True) == Scope.Class
 
     def test_parametrize_and_id(self) -> None:
         def func(x, y):
@@ -286,7 +296,7 @@ class A:
         deadline=400.0
     )  # very close to std deadline and CI boxes are not reliable in CPU power
     def test_idval_hypothesis(self, value) -> None:
-        escaped = _idval(value, "a", 6, None, nodeid=None, config=None)
+        escaped = IdMaker([], [], None, None, None, None, None)._idval(value, "a", 6)
         assert isinstance(escaped, str)
         escaped.encode("ascii")
 
@@ -308,7 +318,10 @@ def test_unicode_idval(self) -> None:
             ),
         ]
         for val, expected in values:
-            assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected
+            assert (
+                IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
+                == expected
+            )
 
     def test_unicode_idval_with_config(self) -> None:
         """Unit test for expected behavior to obtain ids with
@@ -331,12 +344,12 @@ def getini(self, name):
 
         option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support"
 
-        values: List[Tuple[str, Any, str]] = [
+        values: list[tuple[str, Any, str]] = [
             ("ação", MockConfig({option: True}), "ação"),
             ("ação", MockConfig({option: False}), "a\\xe7\\xe3o"),
         ]
         for val, config, expected in values:
-            actual = _idval(val, "a", 6, None, nodeid=None, config=config)
+            actual = IdMaker([], [], None, None, config, None, None)._idval(val, "a", 6)
             assert actual == expected
 
     def test_bytes_idval(self) -> None:
@@ -349,7 +362,10 @@ def test_bytes_idval(self) -> None:
             ("αρά".encode(), r"\xce\xb1\xcf\x81\xce\xac"),
         ]
         for val, expected in values:
-            assert _idval(val, "a", 6, idfn=None, nodeid=None, config=None) == expected
+            assert (
+                IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
+                == expected
+            )
 
     def test_class_or_function_idval(self) -> None:
         """Unit test for the expected behavior to obtain ids for parametrized
@@ -363,7 +379,10 @@ def test_function():
 
         values = [(TestClass, "TestClass"), (test_function, "test_function")]
         for val, expected in values:
-            assert _idval(val, "a", 6, None, nodeid=None, config=None) == expected
+            assert (
+                IdMaker([], [], None, None, None, None, None)._idval(val, "a", 6)
+                == expected
+            )
 
     def test_notset_idval(self) -> None:
         """Test that a NOTSET value (used by an empty parameterset) generates
@@ -371,29 +390,47 @@ def test_notset_idval(self) -> None:
 
         Regression test for #7686.
         """
-        assert _idval(NOTSET, "a", 0, None, nodeid=None, config=None) == "a0"
+        assert (
+            IdMaker([], [], None, None, None, None, None)._idval(NOTSET, "a", 0) == "a0"
+        )
 
     def test_idmaker_autoname(self) -> None:
         """#250"""
-        result = idmaker(
-            ("a", "b"), [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)]
-        )
+        result = IdMaker(
+            ("a", "b"),
+            [pytest.param("string", 1.0), pytest.param("st-ring", 2.0)],
+            None,
+            None,
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == ["string-1.0", "st-ring-2.0"]
 
-        result = idmaker(
-            ("a", "b"), [pytest.param(object(), 1.0), pytest.param(object(), object())]
-        )
+        result = IdMaker(
+            ("a", "b"),
+            [pytest.param(object(), 1.0), pytest.param(object(), object())],
+            None,
+            None,
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == ["a0-1.0", "a1-b1"]
         # unicode mixing, issue250
-        result = idmaker(("a", "b"), [pytest.param({}, b"\xc3\xb4")])
+        result = IdMaker(
+            ("a", "b"), [pytest.param({}, b"\xc3\xb4")], None, None, None, None, None
+        ).make_unique_parameterset_ids()
         assert result == ["a0-\\xc3\\xb4"]
 
     def test_idmaker_with_bytes_regex(self) -> None:
-        result = idmaker(("a"), [pytest.param(re.compile(b"foo"), 1.0)])
+        result = IdMaker(
+            ("a"), [pytest.param(re.compile(b"foo"), 1.0)], None, None, None, None, None
+        ).make_unique_parameterset_ids()
         assert result == ["foo"]
 
     def test_idmaker_native_strings(self) -> None:
-        result = idmaker(
+        result = IdMaker(
             ("a", "b"),
             [
                 pytest.param(1.0, -1.1),
@@ -410,7 +447,12 @@ def test_idmaker_native_strings(self) -> None:
                 pytest.param(b"\xc3\xb4", "other"),
                 pytest.param(1.0j, -2.0j),
             ],
-        )
+            None,
+            None,
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == [
             "1.0--1.1",
             "2--202",
@@ -428,7 +470,7 @@ def test_idmaker_native_strings(self) -> None:
         ]
 
     def test_idmaker_non_printable_characters(self) -> None:
-        result = idmaker(
+        result = IdMaker(
             ("s", "n"),
             [
                 pytest.param("\x00", 1),
@@ -438,42 +480,58 @@ def test_idmaker_non_printable_characters(self) -> None:
                 pytest.param("\t", 5),
                 pytest.param(b"\t", 6),
             ],
-        )
+            None,
+            None,
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"]
 
     def test_idmaker_manual_ids_must_be_printable(self) -> None:
-        result = idmaker(
+        result = IdMaker(
             ("s",),
             [
                 pytest.param("x00", id="hello \x00"),
                 pytest.param("x05", id="hello \x05"),
             ],
-        )
+            None,
+            None,
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == ["hello \\x00", "hello \\x05"]
 
     def test_idmaker_enum(self) -> None:
         enum = pytest.importorskip("enum")
         e = enum.Enum("Foo", "one, two")
-        result = idmaker(("a", "b"), [pytest.param(e.one, e.two)])
+        result = IdMaker(
+            ("a", "b"), [pytest.param(e.one, e.two)], None, None, None, None, None
+        ).make_unique_parameterset_ids()
         assert result == ["Foo.one-Foo.two"]
 
     def test_idmaker_idfn(self) -> None:
         """#351"""
 
-        def ids(val: object) -> Optional[str]:
+        def ids(val: object) -> str | None:
             if isinstance(val, Exception):
                 return repr(val)
             return None
 
-        result = idmaker(
+        result = IdMaker(
             ("a", "b"),
             [
                 pytest.param(10.0, IndexError()),
                 pytest.param(20, KeyError()),
                 pytest.param("three", [1, 2, 3]),
             ],
-            idfn=ids,
-        )
+            ids,
+            None,
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == ["10.0-IndexError()", "20-KeyError()", "three-b2"]
 
     def test_idmaker_idfn_unique_names(self) -> None:
@@ -482,15 +540,19 @@ def test_idmaker_idfn_unique_names(self) -> None:
         def ids(val: object) -> str:
             return "a"
 
-        result = idmaker(
+        result = IdMaker(
             ("a", "b"),
             [
                 pytest.param(10.0, IndexError()),
                 pytest.param(20, KeyError()),
                 pytest.param("three", [1, 2, 3]),
             ],
-            idfn=ids,
-        )
+            ids,
+            None,
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == ["a-a0", "a-a1", "a-a2"]
 
     def test_idmaker_with_idfn_and_config(self) -> None:
@@ -515,17 +577,20 @@ def getini(self, name):
 
         option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support"
 
-        values: List[Tuple[Any, str]] = [
+        values: list[tuple[Any, str]] = [
             (MockConfig({option: True}), "ação"),
             (MockConfig({option: False}), "a\\xe7\\xe3o"),
         ]
         for config, expected in values:
-            result = idmaker(
+            result = IdMaker(
                 ("a",),
                 [pytest.param("string")],
-                idfn=lambda _: "ação",
-                config=config,
-            )
+                lambda _: "ação",
+                None,
+                config,
+                None,
+                None,
+            ).make_unique_parameterset_ids()
             assert result == [expected]
 
     def test_idmaker_with_ids_and_config(self) -> None:
@@ -550,19 +615,54 @@ def getini(self, name):
 
         option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support"
 
-        values: List[Tuple[Any, str]] = [
+        values: list[tuple[Any, str]] = [
+            (MockConfig({option: True}), "ação"),
+            (MockConfig({option: False}), "a\\xe7\\xe3o"),
+        ]
+        for config, expected in values:
+            result = IdMaker(
+                ("a",), [pytest.param("string")], None, ["ação"], config, None, None
+            ).make_unique_parameterset_ids()
+            assert result == [expected]
+
+    def test_idmaker_with_param_id_and_config(self) -> None:
+        """Unit test for expected behavior to create ids with pytest.param(id=...) and
+        disable_test_id_escaping_and_forfeit_all_rights_to_community_support
+        option (#9037).
+        """
+
+        class MockConfig:
+            def __init__(self, config):
+                self.config = config
+
+            def getini(self, name):
+                return self.config[name]
+
+        option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support"
+
+        values: list[tuple[Any, str]] = [
             (MockConfig({option: True}), "ação"),
             (MockConfig({option: False}), "a\\xe7\\xe3o"),
         ]
         for config, expected in values:
-            result = idmaker(
+            result = IdMaker(
                 ("a",),
-                [pytest.param("string")],
-                ids=["ação"],
-                config=config,
-            )
+                [pytest.param("string", id="ação")],
+                None,
+                None,
+                config,
+                None,
+                None,
+            ).make_unique_parameterset_ids()
             assert result == [expected]
 
+    def test_idmaker_duplicated_empty_str(self) -> None:
+        """Regression test for empty strings parametrized more than once (#11563)."""
+        result = IdMaker(
+            ("a",), [pytest.param(""), pytest.param("")], None, None, None, None, None
+        ).make_unique_parameterset_ids()
+        assert result == ["0", "1"]
+
     def test_parametrize_ids_exception(self, pytester: Pytester) -> None:
         """
         :param pytester: the instance of Pytester class, a temporary
@@ -617,23 +717,39 @@ def test_int(arg):
         )
 
     def test_idmaker_with_ids(self) -> None:
-        result = idmaker(
-            ("a", "b"), [pytest.param(1, 2), pytest.param(3, 4)], ids=["a", None]
-        )
+        result = IdMaker(
+            ("a", "b"),
+            [pytest.param(1, 2), pytest.param(3, 4)],
+            None,
+            ["a", None],
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == ["a", "3-4"]
 
     def test_idmaker_with_paramset_id(self) -> None:
-        result = idmaker(
+        result = IdMaker(
             ("a", "b"),
             [pytest.param(1, 2, id="me"), pytest.param(3, 4, id="you")],
-            ids=["a", None],
-        )
+            None,
+            ["a", None],
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == ["me", "you"]
 
     def test_idmaker_with_ids_unique_names(self) -> None:
-        result = idmaker(
-            ("a"), map(pytest.param, [1, 2, 3, 4, 5]), ids=["a", "a", "b", "c", "b"]
-        )
+        result = IdMaker(
+            ("a"),
+            list(map(pytest.param, [1, 2, 3, 4, 5])),
+            None,
+            ["a", "a", "b", "c", "b"],
+            None,
+            None,
+            None,
+        ).make_unique_parameterset_ids()
         assert result == ["a0", "a1", "b0", "c", "b1"]
 
     def test_parametrize_indirect(self) -> None:
@@ -646,8 +762,6 @@ def func(x, y):
         metafunc.parametrize("x", [1], indirect=True)
         metafunc.parametrize("y", [2, 3], indirect=True)
         assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].funcargs == {}
-        assert metafunc._calls[1].funcargs == {}
         assert metafunc._calls[0].params == dict(x=1, y=2)
         assert metafunc._calls[1].params == dict(x=1, y=3)
 
@@ -659,8 +773,10 @@ def func(x, y):
 
         metafunc = self.Metafunc(func)
         metafunc.parametrize("x, y", [("a", "b")], indirect=["x"])
-        assert metafunc._calls[0].funcargs == dict(y="b")
-        assert metafunc._calls[0].params == dict(x="a")
+        assert metafunc._calls[0].params == dict(x="a", y="b")
+        # Since `y` is a direct parameter, its pseudo-fixture would
+        # be registered.
+        assert list(metafunc._arg2fixturedefs.keys()) == ["y"]
 
     def test_parametrize_indirect_list_all(self) -> None:
         """#714"""
@@ -670,8 +786,8 @@ def func(x, y):
 
         metafunc = self.Metafunc(func)
         metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "y"])
-        assert metafunc._calls[0].funcargs == {}
         assert metafunc._calls[0].params == dict(x="a", y="b")
+        assert list(metafunc._arg2fixturedefs.keys()) == []
 
     def test_parametrize_indirect_list_empty(self) -> None:
         """#714"""
@@ -681,8 +797,8 @@ def func(x, y):
 
         metafunc = self.Metafunc(func)
         metafunc.parametrize("x, y", [("a", "b")], indirect=[])
-        assert metafunc._calls[0].funcargs == dict(x="a", y="b")
-        assert metafunc._calls[0].params == {}
+        assert metafunc._calls[0].params == dict(x="a", y="b")
+        assert list(metafunc._arg2fixturedefs.keys()) == ["x", "y"]
 
     def test_parametrize_indirect_wrong_type(self) -> None:
         def func(x, y):
@@ -876,9 +992,9 @@ def test_parametrize_onearg(self) -> None:
         metafunc = self.Metafunc(lambda x: None)
         metafunc.parametrize("x", [1, 2])
         assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].funcargs == dict(x=1)
+        assert metafunc._calls[0].params == dict(x=1)
         assert metafunc._calls[0].id == "1"
-        assert metafunc._calls[1].funcargs == dict(x=2)
+        assert metafunc._calls[1].params == dict(x=2)
         assert metafunc._calls[1].id == "2"
 
     def test_parametrize_onearg_indirect(self) -> None:
@@ -893,11 +1009,45 @@ def test_parametrize_twoargs(self) -> None:
         metafunc = self.Metafunc(lambda x, y: None)
         metafunc.parametrize(("x", "y"), [(1, 2), (3, 4)])
         assert len(metafunc._calls) == 2
-        assert metafunc._calls[0].funcargs == dict(x=1, y=2)
+        assert metafunc._calls[0].params == dict(x=1, y=2)
         assert metafunc._calls[0].id == "1-2"
-        assert metafunc._calls[1].funcargs == dict(x=3, y=4)
+        assert metafunc._calls[1].params == dict(x=3, y=4)
         assert metafunc._calls[1].id == "3-4"
 
+    def test_high_scoped_parametrize_reordering(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize("arg2", [3, 4])
+            @pytest.mark.parametrize("arg1", [0, 1, 2], scope='module')
+            def test1(arg1, arg2):
+                pass
+
+            def test2():
+                pass
+
+            @pytest.mark.parametrize("arg1", [0, 1, 2], scope='module')
+            def test3(arg1):
+                pass
+        """
+        )
+        result = pytester.runpytest("--collect-only")
+        result.stdout.re_match_lines(
+            [
+                r"    <Function test1\[0-3\]>",
+                r"    <Function test3\[0\]>",
+                r"    <Function test1\[0-4\]>",
+                r"    <Function test3\[1\]>",
+                r"    <Function test1\[1-3\]>",
+                r"    <Function test3\[2\]>",
+                r"    <Function test1\[1-4\]>",
+                r"    <Function test1\[2-3\]>",
+                r"    <Function test1\[2-4\]>",
+                r"    <Function test2>",
+            ]
+        )
+
     def test_parametrize_multiple_times(self, pytester: Pytester) -> None:
         pytester.makepyfile(
             """
@@ -969,27 +1119,6 @@ def test_3(self, arg, arg2):
         """
         )
 
-    def test_format_args(self) -> None:
-        def function1():
-            pass
-
-        assert _format_args(function1) == "()"
-
-        def function2(arg1):
-            pass
-
-        assert _format_args(function2) == "(arg1)"
-
-        def function3(arg1, arg2="qwe"):
-            pass
-
-        assert _format_args(function3) == "(arg1, arg2='qwe')"
-
-        def function4(arg1, *args, **kwargs):
-            pass
-
-        assert _format_args(function4) == "(arg1, *args, **kwargs)"
-
 
 class TestMetafuncFunctional:
     def test_attributes(self, pytester: Pytester) -> None:
@@ -1272,7 +1401,7 @@ def test_parametrized_ids_invalid_type(self, pytester: Pytester) -> None:
             """
             import pytest
 
-            @pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, type))
+            @pytest.mark.parametrize("x, expected", [(1, 2), (3, 4), (5, 6)], ids=(None, 2, OSError()))
             def test_ids_numbers(x,expected):
                 assert x * 2 == expected
         """
@@ -1280,8 +1409,8 @@ def test_ids_numbers(x,expected):
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(
             [
-                "In test_ids_numbers: ids must be list of string/float/int/bool,"
-                " found: <class 'type'> (type: <class 'type'>) at index 2"
+                "In test_ids_numbers: ids contains unsupported value OSError() (type: <class 'OSError'>) at index 2. "
+                "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
             ]
         )
 
@@ -1310,13 +1439,13 @@ def test_parametrize_scope_overrides(
         self, pytester: Pytester, scope: str, length: int
     ) -> None:
         pytester.makepyfile(
-            """
+            f"""
             import pytest
             values = []
             def pytest_generate_tests(metafunc):
                 if "arg" in metafunc.fixturenames:
                     metafunc.parametrize("arg", [1,2], indirect=True,
-                                         scope=%r)
+                                         scope={scope!r})
             @pytest.fixture
             def arg(request):
                 values.append(request.param)
@@ -1326,9 +1455,8 @@ def test_hello(arg):
             def test_world(arg):
                 assert arg in (1,2)
             def test_checklength():
-                assert len(values) == %d
+                assert len(values) == {length}
         """
-            % (scope, length)
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=5)
@@ -1376,7 +1504,8 @@ def test_generate_tests_only_done_in_subdir(self, pytester: Pytester) -> None:
                 def pytest_generate_tests(metafunc):
                     assert metafunc.function.__name__ == "test_1"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         sub2.joinpath("conftest.py").write_text(
             textwrap.dedent(
@@ -1384,10 +1513,15 @@ def pytest_generate_tests(metafunc):
                 def pytest_generate_tests(metafunc):
                     assert metafunc.function.__name__ == "test_2"
                 """
-            )
+            ),
+            encoding="utf-8",
+        )
+        sub1.joinpath("test_in_sub1.py").write_text(
+            "def test_1(): pass", encoding="utf-8"
+        )
+        sub2.joinpath("test_in_sub2.py").write_text(
+            "def test_2(): pass", encoding="utf-8"
         )
-        sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass")
-        sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass")
         result = pytester.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1)
         result.assert_outcomes(passed=3)
 
@@ -1420,7 +1554,7 @@ def test_foo(x):
                 pass
         """
         )
-        result = pytester.runpytest("--collectonly")
+        result = pytester.runpytest("--collect-only")
         result.stdout.fnmatch_lines(
             [
                 "collected 0 items / 1 error",
@@ -1435,6 +1569,115 @@ def test_foo(x):
             ]
         )
 
+    @pytest.mark.parametrize("scope", ["class", "package"])
+    def test_parametrize_missing_scope_doesnt_crash(
+        self, pytester: Pytester, scope: str
+    ) -> None:
+        """Doesn't crash when parametrize(scope=<scope>) is used without a
+        corresponding <scope> node."""
+        pytester.makepyfile(
+            f"""
+            import pytest
+
+            @pytest.mark.parametrize("x", [0], scope="{scope}")
+            def test_it(x): pass
+            """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_parametrize_module_level_test_with_class_scope(
+        self, pytester: Pytester
+    ) -> None:
+        """
+        Test that a class-scoped parametrization without a corresponding `Class`
+        gets module scope, i.e. we only create a single FixtureDef for it per module.
+        """
+        module = pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize("x", [0, 1], scope="class")
+            def test_1(x):
+                pass
+
+            @pytest.mark.parametrize("x", [1, 2], scope="module")
+            def test_2(x):
+                pass
+        """
+        )
+        test_1_0, _, test_2_0, _ = pytester.genitems((pytester.getmodulecol(module),))
+
+        assert isinstance(test_1_0, Function)
+        assert test_1_0.name == "test_1[0]"
+        test_1_fixture_x = test_1_0._fixtureinfo.name2fixturedefs["x"][-1]
+
+        assert isinstance(test_2_0, Function)
+        assert test_2_0.name == "test_2[1]"
+        test_2_fixture_x = test_2_0._fixtureinfo.name2fixturedefs["x"][-1]
+
+        assert test_1_fixture_x is test_2_fixture_x
+
+    def test_reordering_with_scopeless_and_just_indirect_parametrization(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makeconftest(
+            """
+            import pytest
+
+            @pytest.fixture(scope="package")
+            def fixture1():
+                pass
+            """
+        )
+        pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.fixture(scope="module")
+            def fixture0():
+                pass
+
+            @pytest.fixture(scope="module")
+            def fixture1(fixture0):
+                pass
+
+            @pytest.mark.parametrize("fixture1", [0], indirect=True)
+            def test_0(fixture1):
+                pass
+
+            @pytest.fixture(scope="module")
+            def fixture():
+                pass
+
+            @pytest.mark.parametrize("fixture", [0], indirect=True)
+            def test_1(fixture):
+                pass
+
+            def test_2():
+                pass
+
+            class Test:
+                @pytest.fixture(scope="class")
+                def fixture(self, fixture):
+                    pass
+
+                @pytest.mark.parametrize("fixture", [0], indirect=True)
+                def test_3(self, fixture):
+                    pass
+            """
+        )
+        result = pytester.runpytest("-v")
+        assert result.ret == 0
+        result.stdout.fnmatch_lines(
+            [
+                "*test_0*",
+                "*test_1*",
+                "*test_2*",
+                "*test_3*",
+            ]
+        )
+
 
 class TestMetafuncFunctionalAuto:
     """Tests related to automatically find out the correct scope for
@@ -1533,9 +1776,9 @@ def test_parametrize_some_arguments_auto_scope(
         self, pytester: Pytester, monkeypatch
     ) -> None:
         """Integration test for (#3941)"""
-        class_fix_setup: List[object] = []
+        class_fix_setup: list[object] = []
         monkeypatch.setattr(sys, "class_fix_setup", class_fix_setup, raising=False)
-        func_fix_setup: List[object] = []
+        func_fix_setup: list[object] = []
         monkeypatch.setattr(sys, "func_fix_setup", func_fix_setup, raising=False)
 
         pytester.makepyfile(
@@ -1725,7 +1968,7 @@ def test_increment(n, expected):
 
     @pytest.mark.parametrize("strict", [True, False])
     def test_xfail_passing_is_xpass(self, pytester: Pytester, strict: bool) -> None:
-        s = """
+        s = f"""
             import pytest
 
             m = pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})
@@ -1737,9 +1980,7 @@ def test_xfail_passing_is_xpass(self, pytester: Pytester, strict: bool) -> None:
             ])
             def test_increment(n, expected):
                 assert n + 1 == expected
-        """.format(
-            strict=strict
-        )
+        """
         pytester.makepyfile(s)
         reprec = pytester.inline_run()
         passed, failed = (2, 1) if strict else (3, 0)
@@ -1790,7 +2031,7 @@ def test_limit(limit, myfixture):
 
     @pytest.mark.parametrize("strict", [True, False])
     def test_parametrize_marked_value(self, pytester: Pytester, strict: bool) -> None:
-        s = """
+        s = f"""
             import pytest
 
             @pytest.mark.parametrize(("n", "expected"), [
@@ -1805,9 +2046,7 @@ def test_parametrize_marked_value(self, pytester: Pytester, strict: bool) -> Non
             ])
             def test_increment(n, expected):
                 assert n + 1 == expected
-        """.format(
-            strict=strict
-        )
+        """
         pytester.makepyfile(s)
         reprec = pytester.inline_run()
         passed, failed = (0, 2) if strict else (2, 0)
@@ -1905,3 +2144,127 @@ def test_converted_to_str(a, b):
                 "*= 6 passed in *",
             ]
         )
+
+
+class TestHiddenParam:
+    """Test that pytest.HIDDEN_PARAM works"""
+
+    def test_parametrize_ids(self, pytester: Pytester) -> None:
+        items = pytester.getitems(
+            """
+            import pytest
+
+            @pytest.mark.parametrize(
+                ("foo", "bar"),
+                [
+                    ("a", "x"),
+                    ("b", "y"),
+                    ("c", "z"),
+                ],
+                ids=["paramset1", pytest.HIDDEN_PARAM, "paramset3"],
+            )
+            def test_func(foo, bar):
+                pass
+        """
+        )
+        names = [item.name for item in items]
+        assert names == [
+            "test_func[paramset1]",
+            "test_func",
+            "test_func[paramset3]",
+        ]
+
+    def test_param_id(self, pytester: Pytester) -> None:
+        items = pytester.getitems(
+            """
+            import pytest
+
+            @pytest.mark.parametrize(
+                ("foo", "bar"),
+                [
+                    pytest.param("a", "x", id="paramset1"),
+                    pytest.param("b", "y", id=pytest.HIDDEN_PARAM),
+                    ("c", "z"),
+                ],
+            )
+            def test_func(foo, bar):
+                pass
+        """
+        )
+        names = [item.name for item in items]
+        assert names == [
+            "test_func[paramset1]",
+            "test_func",
+            "test_func[c-z]",
+        ]
+
+    def test_multiple_hidden_param_is_forbidden(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize(
+                ("foo", "bar"),
+                [
+                    ("a", "x"),
+                    ("b", "y"),
+                ],
+                ids=[pytest.HIDDEN_PARAM, pytest.HIDDEN_PARAM],
+            )
+            def test_func(foo, bar):
+                pass
+        """
+        )
+        result = pytester.runpytest("--collect-only")
+        result.stdout.fnmatch_lines(
+            [
+                "collected 0 items / 1 error",
+                "",
+                "*= ERRORS =*",
+                "*_ ERROR collecting test_multiple_hidden_param_is_forbidden.py _*",
+                "E   Failed: In test_func: multiple instances of HIDDEN_PARAM cannot be used "
+                "in the same parametrize call, because the tests names need to be unique.",
+                "*! Interrupted: 1 error during collection !*",
+                "*= no tests collected, 1 error in *",
+            ]
+        )
+
+    def test_multiple_hidden_param_is_forbidden_idmaker(self) -> None:
+        id_maker = IdMaker(
+            ("foo", "bar"),
+            [pytest.param("a", "x"), pytest.param("b", "y")],
+            None,
+            [pytest.HIDDEN_PARAM, pytest.HIDDEN_PARAM],
+            None,
+            "some_node_id",
+            None,
+        )
+        expected = "In some_node_id: multiple instances of HIDDEN_PARAM"
+        with pytest.raises(Failed, match=expected):
+            id_maker.make_unique_parameterset_ids()
+
+    def test_multiple_parametrize(self, pytester: Pytester) -> None:
+        items = pytester.getitems(
+            """
+            import pytest
+
+            @pytest.mark.parametrize(
+                "bar",
+                ["x", "y"],
+            )
+            @pytest.mark.parametrize(
+                "foo",
+                ["a", "b"],
+                ids=["a", pytest.HIDDEN_PARAM],
+            )
+            def test_func(foo, bar):
+                pass
+        """
+        )
+        names = [item.name for item in items]
+        assert names == [
+            "test_func[a-x]",
+            "test_func[a-y]",
+            "test_func[x]",
+            "test_func[y]",
+        ]
diff --git a/testing/python/raises.py b/testing/python/raises.py
index 2d62e91091b..3da260d1837 100644
--- a/testing/python/raises.py
+++ b/testing/python/raises.py
@@ -1,9 +1,17 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import re
 import sys
 
-import pytest
 from _pytest.outcomes import Failed
 from _pytest.pytester import Pytester
+from _pytest.warning_types import PytestWarning
+import pytest
+
+
+def wrap_escape(s: str) -> str:
+    return "^" + re.escape(s) + "$"
 
 
 class TestRaises:
@@ -19,6 +27,35 @@ def test_raises_function(self):
         excinfo = pytest.raises(ValueError, int, "hello")
         assert "invalid literal" in str(excinfo.value)
 
+    def test_raises_does_not_allow_none(self):
+        with pytest.raises(
+            ValueError,
+            match=wrap_escape("You must specify at least one parameter to match on."),
+        ):
+            # We're testing that this invalid usage gives a helpful error,
+            # so we can ignore Mypy telling us that None is invalid.
+            pytest.raises(expected_exception=None)  # type: ignore
+
+    # it's unclear if this message is helpful, and if it is, should it trigger more
+    # liberally? Usually you'd get a TypeError here
+    def test_raises_false_and_arg(self):
+        with pytest.raises(
+            ValueError,
+            match=wrap_escape(
+                "Expected an exception type or a tuple of exception types, but got `False`. "
+                "Raising exceptions is already understood as failing the test, so you don't need "
+                "any special code to say 'this should never raise an exception'."
+            ),
+        ):
+            pytest.raises(False, int)  # type: ignore[call-overload]
+
+    def test_raises_does_not_allow_empty_tuple(self):
+        with pytest.raises(
+            ValueError,
+            match=wrap_escape("You must specify at least one parameter to match on."),
+        ):
+            pytest.raises(expected_exception=())
+
     def test_raises_callable_no_exception(self) -> None:
         class A:
             def __call__(self):
@@ -82,13 +119,9 @@ def test_raise_wrong_exception_passes_by():
     def test_does_not_raise(self, pytester: Pytester) -> None:
         pytester.makepyfile(
             """
-            from contextlib import contextmanager
+            from contextlib import nullcontext as does_not_raise
             import pytest
 
-            @contextmanager
-            def does_not_raise():
-                yield
-
             @pytest.mark.parametrize('example_input,expectation', [
                 (3, does_not_raise()),
                 (2, does_not_raise()),
@@ -107,13 +140,9 @@ def test_division(example_input, expectation):
     def test_does_not_raise_does_raise(self, pytester: Pytester) -> None:
         pytester.makepyfile(
             """
-            from contextlib import contextmanager
+            from contextlib import nullcontext as does_not_raise
             import pytest
 
-            @contextmanager
-            def does_not_raise():
-                yield
-
             @pytest.mark.parametrize('example_input,expectation', [
                 (0, does_not_raise()),
                 (1, pytest.raises(ZeroDivisionError)),
@@ -127,6 +156,26 @@ def test_division(example_input, expectation):
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(["*2 failed*"])
 
+    def test_raises_with_invalid_regex(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            import pytest
+
+            def test_invalid_regex():
+                with pytest.raises(ValueError, match="invalid regex character ["):
+                    raise ValueError()
+            """
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(
+            [
+                "*Invalid regex pattern provided to 'match': unterminated character set at position 24*",
+            ]
+        )
+        result.stdout.no_fnmatch_line("*Traceback*")
+        result.stdout.no_fnmatch_line("*File*")
+        result.stdout.no_fnmatch_line("*line*")
+
     def test_noclass(self) -> None:
         with pytest.raises(TypeError):
             pytest.raises("wrong", lambda: None)  # type: ignore[call-overload]
@@ -144,7 +193,7 @@ def test_no_raise_message(self) -> None:
         try:
             pytest.raises(ValueError, int, "0")
         except pytest.fail.Exception as e:
-            assert e.msg == f"DID NOT RAISE {repr(ValueError)}"
+            assert e.msg == f"DID NOT RAISE {ValueError!r}"
         else:
             assert False, "Expected pytest.raises.Exception"
 
@@ -152,11 +201,13 @@ def test_no_raise_message(self) -> None:
             with pytest.raises(ValueError):
                 pass
         except pytest.fail.Exception as e:
-            assert e.msg == f"DID NOT RAISE {repr(ValueError)}"
+            assert e.msg == f"DID NOT RAISE {ValueError!r}"
         else:
             assert False, "Expected pytest.raises.Exception"
 
-    @pytest.mark.parametrize("method", ["function", "function_match", "with"])
+    @pytest.mark.parametrize(
+        "method", ["function", "function_match", "with", "with_raisesexc", "with_group"]
+    )
     def test_raises_cyclic_reference(self, method):
         """Ensure pytest.raises does not leave a reference cycle (#1965)."""
         import gc
@@ -172,9 +223,17 @@ def __call__(self):
             pytest.raises(ValueError, t)
         elif method == "function_match":
             pytest.raises(ValueError, t).match("^$")
-        else:
+        elif method == "with":
             with pytest.raises(ValueError):
                 t()
+        elif method == "with_raisesexc":
+            with pytest.RaisesExc(ValueError):
+                t()
+        elif method == "with_group":
+            with pytest.RaisesGroup(ValueError, allow_unwrapped=True):
+                t()
+        else:  # pragma: no cover
+            raise AssertionError("bad parametrization")
 
         # ensure both forms of pytest.raises don't leave exceptions in sys.exc_info()
         assert sys.exc_info() == (None, None, None)
@@ -191,10 +250,12 @@ def test_raises_match(self) -> None:
             int("asdf")
 
         msg = "with base 16"
-        expr = "Regex pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\".".format(
-            msg
+        expr = (
+            "Regex pattern did not match.\n"
+            f" Regex: {msg!r}\n"
+            " Input: \"invalid literal for int() with base 10: 'asdf'\""
         )
-        with pytest.raises(AssertionError, match=re.escape(expr)):
+        with pytest.raises(AssertionError, match="^" + re.escape(expr) + "$"):
             with pytest.raises(ValueError, match=msg):
                 int("asdf", base=10)
 
@@ -212,12 +273,22 @@ def tfunc(match):
         pytest.raises(ValueError, tfunc, match="asdf").match("match=asdf")
         pytest.raises(ValueError, tfunc, match="").match("match=")
 
+        # empty string matches everything, which is probably not what the user wants
+        with pytest.warns(
+            PytestWarning,
+            match=wrap_escape(
+                "matching against an empty string will *always* pass. If you want to check for an empty message you "
+                "need to pass '^$'. If you don't want to match you should pass `None` or leave out the parameter."
+            ),
+        ):
+            pytest.raises(match="")
+
     def test_match_failure_string_quoting(self):
         with pytest.raises(AssertionError) as excinfo:
             with pytest.raises(AssertionError, match="'foo"):
                 raise AssertionError("'bar")
         (msg,) = excinfo.value.args
-        assert msg == 'Regex pattern "\'foo" does not match "\'bar".'
+        assert msg == '''Regex pattern did not match.\n Regex: "'foo"\n Input: "'bar"'''
 
     def test_match_failure_exact_string_message(self):
         message = "Oh here is a message with (42) numbers in parameters"
@@ -226,9 +297,10 @@ def test_match_failure_exact_string_message(self):
                 raise AssertionError(message)
         (msg,) = excinfo.value.args
         assert msg == (
-            "Regex pattern 'Oh here is a message with (42) numbers in "
-            "parameters' does not match 'Oh here is a message with (42) "
-            "numbers in parameters'. Did you mean to `re.escape()` the regex?"
+            "Regex pattern did not match.\n"
+            " Regex: 'Oh here is a message with (42) numbers in parameters'\n"
+            " Input: 'Oh here is a message with (42) numbers in parameters'\n"
+            " Did you mean to `re.escape()` the regex?"
         )
 
     def test_raises_match_wrong_type(self):
@@ -237,7 +309,10 @@ def test_raises_match_wrong_type(self):
         pytest should throw the unexpected exception - the pattern match is not
         really relevant if we got a different exception.
         """
-        with pytest.raises(ValueError):
+        with pytest.raises(
+            ValueError,
+            match=wrap_escape("invalid literal for int() with base 10: 'asdf'"),
+        ):
             with pytest.raises(IndexError, match="nomatch"):
                 int("asdf")
 
@@ -273,26 +348,58 @@ def __class__(self):
         assert "via __class__" in excinfo.value.args[0]
 
     def test_raises_context_manager_with_kwargs(self):
-        with pytest.raises(TypeError) as excinfo:
-            with pytest.raises(Exception, foo="bar"):  # type: ignore[call-overload]
+        with pytest.raises(expected_exception=ValueError):
+            raise ValueError
+        with pytest.raises(
+            TypeError,
+            match=wrap_escape(
+                "Unexpected keyword arguments passed to pytest.raises: foo\n"
+                "Use context-manager form instead?"
+            ),
+        ):
+            with pytest.raises(OSError, foo="bar"):  # type: ignore[call-overload]
                 pass
-        assert "Unexpected keyword arguments" in str(excinfo.value)
 
     def test_expected_exception_is_not_a_baseexception(self) -> None:
-        with pytest.raises(TypeError) as excinfo:
+        with pytest.raises(
+            TypeError,
+            match=wrap_escape(
+                "expected exception must be a BaseException type, not 'str'"
+            ),
+        ):
             with pytest.raises("hello"):  # type: ignore[call-overload]
                 pass  # pragma: no cover
-        assert "must be a BaseException type, not str" in str(excinfo.value)
 
         class NotAnException:
             pass
 
-        with pytest.raises(TypeError) as excinfo:
+        with pytest.raises(
+            ValueError,
+            match=wrap_escape(
+                "expected exception must be a BaseException type, not 'NotAnException'"
+            ),
+        ):
             with pytest.raises(NotAnException):  # type: ignore[type-var]
                 pass  # pragma: no cover
-        assert "must be a BaseException type, not NotAnException" in str(excinfo.value)
 
-        with pytest.raises(TypeError) as excinfo:
+        with pytest.raises(
+            TypeError,
+            match=wrap_escape(
+                "expected exception must be a BaseException type, not 'str'"
+            ),
+        ):
             with pytest.raises(("hello", NotAnException)):  # type: ignore[arg-type]
                 pass  # pragma: no cover
-        assert "must be a BaseException type, not str" in str(excinfo.value)
+
+    def test_issue_11872(self) -> None:
+        """Regression test for #11872.
+
+        urllib.error.HTTPError on Python<=3.9 raises KeyError instead of
+        AttributeError on invalid attribute access.
+
+        https://github.com/python/cpython/issues/98778
+        """
+        from urllib.error import HTTPError
+
+        with pytest.raises(HTTPError, match="Not Found"):
+            raise HTTPError(code=404, msg="Not Found", fp=None, hdrs=None, url="")  # type: ignore [arg-type]
diff --git a/testing/python/raises_group.py b/testing/python/raises_group.py
new file mode 100644
index 00000000000..04979c32e98
--- /dev/null
+++ b/testing/python/raises_group.py
@@ -0,0 +1,1348 @@
+from __future__ import annotations
+
+# several expected multi-line strings contain long lines. We don't wanna break them up
+# as that makes it confusing to see where the line breaks are.
+# ruff: noqa: E501
+from contextlib import AbstractContextManager
+import re
+import sys
+
+from _pytest._code import ExceptionInfo
+from _pytest.outcomes import Failed
+from _pytest.pytester import Pytester
+from _pytest.raises import RaisesExc
+from _pytest.raises import RaisesGroup
+from _pytest.raises import repr_callable
+import pytest
+
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import BaseExceptionGroup
+    from exceptiongroup import ExceptionGroup
+
+
+def wrap_escape(s: str) -> str:
+    return "^" + re.escape(s) + "$"
+
+
+def fails_raises_group(msg: str, add_prefix: bool = True) -> RaisesExc[Failed]:
+    assert msg[-1] != "\n", (
+        "developer error, expected string should not end with newline"
+    )
+    prefix = "Raised exception group did not match: " if add_prefix else ""
+    return pytest.raises(Failed, match=wrap_escape(prefix + msg))
+
+
+def test_raises_group() -> None:
+    with pytest.raises(
+        TypeError,
+        match=wrap_escape("expected exception must be a BaseException type, not 'int'"),
+    ):
+        RaisesExc(5)  # type: ignore[call-overload]
+    with pytest.raises(
+        ValueError,
+        match=wrap_escape("expected exception must be a BaseException type, not 'int'"),
+    ):
+        RaisesExc(int)  # type: ignore[type-var]
+    with pytest.raises(
+        TypeError,
+        # TODO: bad sentence structure
+        match=wrap_escape(
+            "expected exception must be a BaseException type, RaisesExc, or RaisesGroup, not an exception instance (ValueError)",
+        ),
+    ):
+        RaisesGroup(ValueError())  # type: ignore[call-overload]
+    with RaisesGroup(ValueError):
+        raise ExceptionGroup("foo", (ValueError(),))
+
+    with (
+        fails_raises_group("`SyntaxError()` is not an instance of `ValueError`"),
+        RaisesGroup(ValueError),
+    ):
+        raise ExceptionGroup("foo", (SyntaxError(),))
+
+    # multiple exceptions
+    with RaisesGroup(ValueError, SyntaxError):
+        raise ExceptionGroup("foo", (ValueError(), SyntaxError()))
+
+    # order doesn't matter
+    with RaisesGroup(SyntaxError, ValueError):
+        raise ExceptionGroup("foo", (ValueError(), SyntaxError()))
+
+    # nested exceptions
+    with RaisesGroup(RaisesGroup(ValueError)):
+        raise ExceptionGroup("foo", (ExceptionGroup("bar", (ValueError(),)),))
+
+    with RaisesGroup(
+        SyntaxError,
+        RaisesGroup(ValueError),
+        RaisesGroup(RuntimeError),
+    ):
+        raise ExceptionGroup(
+            "foo",
+            (
+                SyntaxError(),
+                ExceptionGroup("bar", (ValueError(),)),
+                ExceptionGroup("", (RuntimeError(),)),
+            ),
+        )
+
+
+def test_incorrect_number_exceptions() -> None:
+    # We previously gave an error saying the number of exceptions was wrong,
+    # but we now instead indicate excess/missing exceptions
+    with (
+        fails_raises_group(
+            "1 matched exception. Unexpected exception(s): [RuntimeError()]"
+        ),
+        RaisesGroup(ValueError),
+    ):
+        raise ExceptionGroup("", (RuntimeError(), ValueError()))
+
+    # will error if there's missing exceptions
+    with (
+        fails_raises_group(
+            "1 matched exception. Too few exceptions raised, found no match for: [SyntaxError]"
+        ),
+        RaisesGroup(ValueError, SyntaxError),
+    ):
+        raise ExceptionGroup("", (ValueError(),))
+
+    with (
+        fails_raises_group(
+            "\n"
+            "1 matched exception. \n"
+            "Too few exceptions raised!\n"
+            "The following expected exceptions did not find a match:\n"
+            "  ValueError\n"
+            "    It matches `ValueError()` which was paired with `ValueError`"
+        ),
+        RaisesGroup(ValueError, ValueError),
+    ):
+        raise ExceptionGroup("", (ValueError(),))
+
+    with (
+        fails_raises_group(
+            "\n"
+            "1 matched exception. \n"
+            "Unexpected exception(s)!\n"
+            "The following raised exceptions did not find a match\n"
+            "  ValueError('b'):\n"
+            "    It matches `ValueError` which was paired with `ValueError('a')`"
+        ),
+        RaisesGroup(ValueError),
+    ):
+        raise ExceptionGroup("", (ValueError("a"), ValueError("b")))
+
+    with (
+        fails_raises_group(
+            "\n"
+            "1 matched exception. \n"
+            "The following expected exceptions did not find a match:\n"
+            "  ValueError\n"
+            "    It matches `ValueError()` which was paired with `ValueError`\n"
+            "The following raised exceptions did not find a match\n"
+            "  SyntaxError():\n"
+            "    `SyntaxError()` is not an instance of `ValueError`"
+        ),
+        RaisesGroup(ValueError, ValueError),
+    ):
+        raise ExceptionGroup("", [ValueError(), SyntaxError()])
+
+
+def test_flatten_subgroups() -> None:
+    # loose semantics, as with expect*
+    with RaisesGroup(ValueError, flatten_subgroups=True):
+        raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
+
+    with RaisesGroup(ValueError, TypeError, flatten_subgroups=True):
+        raise ExceptionGroup("", (ExceptionGroup("", (ValueError(), TypeError())),))
+    with RaisesGroup(ValueError, TypeError, flatten_subgroups=True):
+        raise ExceptionGroup("", [ExceptionGroup("", [ValueError()]), TypeError()])
+
+    # mixed loose is possible if you want it to be at least N deep
+    with RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)):
+        raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),))
+    with RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)):
+        raise ExceptionGroup(
+            "",
+            (ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)),),
+        )
+
+    # but not the other way around
+    with pytest.raises(
+        ValueError,
+        match=r"^You cannot specify a nested structure inside a RaisesGroup with",
+    ):
+        RaisesGroup(RaisesGroup(ValueError), flatten_subgroups=True)  # type: ignore[call-overload]
+
+    # flatten_subgroups is not sufficient to catch fully unwrapped
+    with (
+        fails_raises_group(
+            "`ValueError()` is not an exception group, but would match with `allow_unwrapped=True`"
+        ),
+        RaisesGroup(ValueError, flatten_subgroups=True),
+    ):
+        raise ValueError
+    with (
+        fails_raises_group(
+            "RaisesGroup(ValueError, flatten_subgroups=True): `ValueError()` is not an exception group, but would match with `allow_unwrapped=True`"
+        ),
+        RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True)),
+    ):
+        raise ExceptionGroup("", (ValueError(),))
+
+    # helpful suggestion if flatten_subgroups would make it pass
+    with (
+        fails_raises_group(
+            "Raised exception group did not match: \n"
+            "The following expected exceptions did not find a match:\n"
+            "  ValueError\n"
+            "  TypeError\n"
+            "The following raised exceptions did not find a match\n"
+            "  ExceptionGroup('', [ValueError(), TypeError()]):\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `TypeError`\n"
+            "Did you mean to use `flatten_subgroups=True`?",
+            add_prefix=False,
+        ),
+        RaisesGroup(ValueError, TypeError),
+    ):
+        raise ExceptionGroup("", [ExceptionGroup("", [ValueError(), TypeError()])])
+    # but doesn't consider check (otherwise we'd break typing guarantees)
+    with (
+        fails_raises_group(
+            "Raised exception group did not match: \n"
+            "The following expected exceptions did not find a match:\n"
+            "  ValueError\n"
+            "  TypeError\n"
+            "The following raised exceptions did not find a match\n"
+            "  ExceptionGroup('', [ValueError(), TypeError()]):\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `TypeError`\n"
+            "Did you mean to use `flatten_subgroups=True`?",
+            add_prefix=False,
+        ),
+        RaisesGroup(
+            ValueError,
+            TypeError,
+            check=lambda eg: len(eg.exceptions) == 1,
+        ),
+    ):
+        raise ExceptionGroup("", [ExceptionGroup("", [ValueError(), TypeError()])])
+    # correct number of exceptions, and flatten_subgroups would make it pass
+    # This now doesn't print a repr of the caught exception at all, but that can be found in the traceback
+    with (
+        fails_raises_group(
+            "Raised exception group did not match: Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "  Did you mean to use `flatten_subgroups=True`?",
+            add_prefix=False,
+        ),
+        RaisesGroup(ValueError),
+    ):
+        raise ExceptionGroup("", [ExceptionGroup("", [ValueError()])])
+    # correct number of exceptions, but flatten_subgroups wouldn't help, so we don't suggest it
+    with (
+        fails_raises_group(
+            "Unexpected nested `ExceptionGroup()`, expected `ValueError`"
+        ),
+        RaisesGroup(ValueError),
+    ):
+        raise ExceptionGroup("", [ExceptionGroup("", [TypeError()])])
+
+    # flatten_subgroups can be suggested if nested. This will implicitly ask the user to
+    # do `RaisesGroup(RaisesGroup(ValueError, flatten_subgroups=True))` which is unlikely
+    # to be what they actually want - but I don't think it's worth trying to special-case
+    with (
+        fails_raises_group(
+            "RaisesGroup(ValueError): Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "  Did you mean to use `flatten_subgroups=True`?",
+        ),
+        RaisesGroup(RaisesGroup(ValueError)),
+    ):
+        raise ExceptionGroup(
+            "",
+            [ExceptionGroup("", [ExceptionGroup("", [ValueError()])])],
+        )
+
+    # Don't mention "unexpected nested" if expecting an ExceptionGroup.
+    # Although it should perhaps be an error to specify `RaisesGroup(ExceptionGroup)` in
+    # favor of doing `RaisesGroup(RaisesGroup(...))`.
+    with (
+        fails_raises_group(
+            "`BaseExceptionGroup()` is not an instance of `ExceptionGroup`"
+        ),
+        RaisesGroup(ExceptionGroup),
+    ):
+        raise BaseExceptionGroup("", [BaseExceptionGroup("", [KeyboardInterrupt()])])
+
+
+def test_catch_unwrapped_exceptions() -> None:
+    # Catches lone exceptions with strict=False
+    # just as except* would
+    with RaisesGroup(ValueError, allow_unwrapped=True):
+        raise ValueError
+
+    # expecting multiple unwrapped exceptions is not possible
+    with pytest.raises(
+        ValueError,
+        match=r"^You cannot specify multiple exceptions with",
+    ):
+        RaisesGroup(SyntaxError, ValueError, allow_unwrapped=True)  # type: ignore[call-overload]
+    # if users want one of several exception types they need to use a RaisesExc
+    # (which the error message suggests)
+    with RaisesGroup(
+        RaisesExc(check=lambda e: isinstance(e, (SyntaxError, ValueError))),
+        allow_unwrapped=True,
+    ):
+        raise ValueError
+
+    # Unwrapped nested `RaisesGroup` is likely a user error, so we raise an error.
+    with pytest.raises(ValueError, match="has no effect when expecting"):
+        RaisesGroup(RaisesGroup(ValueError), allow_unwrapped=True)  # type: ignore[call-overload]
+
+    # But it *can* be used to check for nesting level +- 1 if they move it to
+    # the nested RaisesGroup. Users should probably use `RaisesExc`s instead though.
+    with RaisesGroup(RaisesGroup(ValueError, allow_unwrapped=True)):
+        raise ExceptionGroup("", [ExceptionGroup("", [ValueError()])])
+    with RaisesGroup(RaisesGroup(ValueError, allow_unwrapped=True)):
+        raise ExceptionGroup("", [ValueError()])
+
+    # with allow_unwrapped=False (default) it will not be caught
+    with (
+        fails_raises_group(
+            "`ValueError()` is not an exception group, but would match with `allow_unwrapped=True`"
+        ),
+        RaisesGroup(ValueError),
+    ):
+        raise ValueError("value error text")
+
+    # allow_unwrapped on its own won't match against nested groups
+    with (
+        fails_raises_group(
+            "Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "  Did you mean to use `flatten_subgroups=True`?",
+        ),
+        RaisesGroup(ValueError, allow_unwrapped=True),
+    ):
+        raise ExceptionGroup("foo", [ExceptionGroup("bar", [ValueError()])])
+
+    # you need both allow_unwrapped and flatten_subgroups to fully emulate except*
+    with RaisesGroup(ValueError, allow_unwrapped=True, flatten_subgroups=True):
+        raise ExceptionGroup("", [ExceptionGroup("", [ValueError()])])
+
+    # code coverage
+    with (
+        fails_raises_group(
+            "Raised exception (group) did not match: `TypeError()` is not an instance of `ValueError`",
+            add_prefix=False,
+        ),
+        RaisesGroup(ValueError, allow_unwrapped=True),
+    ):
+        raise TypeError("this text doesn't show up in the error message")
+    with (
+        fails_raises_group(
+            "Raised exception (group) did not match: RaisesExc(ValueError): `TypeError()` is not an instance of `ValueError`",
+            add_prefix=False,
+        ),
+        RaisesGroup(RaisesExc(ValueError), allow_unwrapped=True),
+    ):
+        raise TypeError
+
+    # check we don't suggest unwrapping with nested RaisesGroup
+    with (
+        fails_raises_group("`ValueError()` is not an exception group"),
+        RaisesGroup(RaisesGroup(ValueError)),
+    ):
+        raise ValueError
+
+
+def test_match() -> None:
+    # supports match string
+    with RaisesGroup(ValueError, match="bar"):
+        raise ExceptionGroup("bar", (ValueError(),))
+
+    # now also works with ^$
+    with RaisesGroup(ValueError, match="^bar$"):
+        raise ExceptionGroup("bar", (ValueError(),))
+
+    # it also includes notes
+    with RaisesGroup(ValueError, match="my note"):
+        e = ExceptionGroup("bar", (ValueError(),))
+        e.add_note("my note")
+        raise e
+
+    # and technically you can match it all with ^$
+    # but you're probably better off using a RaisesExc at that point
+    with RaisesGroup(ValueError, match="^bar\nmy note$"):
+        e = ExceptionGroup("bar", (ValueError(),))
+        e.add_note("my note")
+        raise e
+
+    with (
+        fails_raises_group(
+            "Regex pattern did not match the `ExceptionGroup()`.\n"
+            " Regex: 'foo'\n"
+            " Input: 'bar'"
+        ),
+        RaisesGroup(ValueError, match="foo"),
+    ):
+        raise ExceptionGroup("bar", (ValueError(),))
+
+    # Suggest a fix for easy pitfall of adding match to the RaisesGroup instead of
+    # using a RaisesExc.
+    # This requires a single expected & raised exception, the expected is a type,
+    # and `isinstance(raised, expected_type)`.
+    with (
+        fails_raises_group(
+            "Regex pattern did not match the `ExceptionGroup()`.\n"
+            " Regex: 'foo'\n"
+            " Input: 'bar'\n"
+            " but matched the expected `ValueError`.\n"
+            " You might want `RaisesGroup(RaisesExc(ValueError, match='foo'))`"
+        ),
+        RaisesGroup(ValueError, match="foo"),
+    ):
+        raise ExceptionGroup("bar", [ValueError("foo")])
+
+
+def test_check() -> None:
+    exc = ExceptionGroup("", (ValueError(),))
+
+    def is_exc(e: ExceptionGroup[ValueError]) -> bool:
+        return e is exc
+
+    is_exc_repr = repr_callable(is_exc)
+    with RaisesGroup(ValueError, check=is_exc):
+        raise exc
+
+    with (
+        fails_raises_group(
+            f"check {is_exc_repr} did not return True on the ExceptionGroup"
+        ),
+        RaisesGroup(ValueError, check=is_exc),
+    ):
+        raise ExceptionGroup("", (ValueError(),))
+
+    def is_value_error(e: BaseException) -> bool:
+        return isinstance(e, ValueError)
+
+    # helpful suggestion if the user thinks the check is for the sub-exception
+    with (
+        fails_raises_group(
+            f"check {is_value_error} did not return True on the ExceptionGroup, but did return True for the expected ValueError. You might want RaisesGroup(RaisesExc(ValueError, check=<...>))"
+        ),
+        RaisesGroup(ValueError, check=is_value_error),
+    ):
+        raise ExceptionGroup("", (ValueError(),))
+
+
+def test_unwrapped_match_check() -> None:
+    def my_check(e: object) -> bool:  # pragma: no cover
+        return True
+
+    msg = (
+        "`allow_unwrapped=True` bypasses the `match` and `check` parameters"
+        " if the exception is unwrapped. If you intended to match/check the"
+        " exception you should use a `RaisesExc` object. If you want to match/check"
+        " the exceptiongroup when the exception *is* wrapped you need to"
+        " do e.g. `if isinstance(exc.value, ExceptionGroup):"
+        " assert RaisesGroup(...).matches(exc.value)` afterwards."
+    )
+    with pytest.raises(ValueError, match=re.escape(msg)):
+        RaisesGroup(ValueError, allow_unwrapped=True, match="foo")  # type: ignore[call-overload]
+    with pytest.raises(ValueError, match=re.escape(msg)):
+        RaisesGroup(ValueError, allow_unwrapped=True, check=my_check)  # type: ignore[call-overload]
+
+    # Users should instead use a RaisesExc
+    rg = RaisesGroup(RaisesExc(ValueError, match="^foo$"), allow_unwrapped=True)
+    with rg:
+        raise ValueError("foo")
+    with rg:
+        raise ExceptionGroup("", [ValueError("foo")])
+
+    # or if they wanted to match/check the group, do a conditional `.matches()`
+    with RaisesGroup(ValueError, allow_unwrapped=True) as exc:
+        raise ExceptionGroup("bar", [ValueError("foo")])
+    if isinstance(exc.value, ExceptionGroup):  # pragma: no branch
+        assert RaisesGroup(ValueError, match="bar").matches(exc.value)
+
+
+def test_matches() -> None:
+    rg = RaisesGroup(ValueError)
+    assert not rg.matches(None)
+    assert not rg.matches(ValueError())
+    assert rg.matches(ExceptionGroup("", (ValueError(),)))
+
+    re = RaisesExc(ValueError)
+    assert not re.matches(None)
+    assert re.matches(ValueError())
+
+
+def test_message() -> None:
+    def check_message(
+        message: str,
+        body: RaisesGroup[BaseException],
+    ) -> None:
+        with (
+            pytest.raises(
+                Failed,
+                match=f"^DID NOT RAISE any exception, expected `{re.escape(message)}`$",
+            ),
+            body,
+        ):
+            ...
+
+    # basic
+    check_message("ExceptionGroup(ValueError)", RaisesGroup(ValueError))
+    # multiple exceptions
+    check_message(
+        "ExceptionGroup(ValueError, ValueError)",
+        RaisesGroup(ValueError, ValueError),
+    )
+    # nested
+    check_message(
+        "ExceptionGroup(ExceptionGroup(ValueError))",
+        RaisesGroup(RaisesGroup(ValueError)),
+    )
+
+    # RaisesExc
+    check_message(
+        "ExceptionGroup(RaisesExc(ValueError, match='my_str'))",
+        RaisesGroup(RaisesExc(ValueError, match="my_str")),
+    )
+    check_message(
+        "ExceptionGroup(RaisesExc(match='my_str'))",
+        RaisesGroup(RaisesExc(match="my_str")),
+    )
+    # one-size tuple is printed as not being a tuple
+    check_message(
+        "ExceptionGroup(RaisesExc(ValueError))",
+        RaisesGroup(RaisesExc((ValueError,))),
+    )
+    check_message(
+        "ExceptionGroup(RaisesExc((ValueError, IndexError)))",
+        RaisesGroup(RaisesExc((ValueError, IndexError))),
+    )
+
+    # BaseExceptionGroup
+    check_message(
+        "BaseExceptionGroup(KeyboardInterrupt)",
+        RaisesGroup(KeyboardInterrupt),
+    )
+    # BaseExceptionGroup with type inside RaisesExc
+    check_message(
+        "BaseExceptionGroup(RaisesExc(KeyboardInterrupt))",
+        RaisesGroup(RaisesExc(KeyboardInterrupt)),
+    )
+    check_message(
+        "BaseExceptionGroup(RaisesExc((ValueError, KeyboardInterrupt)))",
+        RaisesGroup(RaisesExc((ValueError, KeyboardInterrupt))),
+    )
+    # Base-ness transfers to parent containers
+    check_message(
+        "BaseExceptionGroup(BaseExceptionGroup(KeyboardInterrupt))",
+        RaisesGroup(RaisesGroup(KeyboardInterrupt)),
+    )
+    # but not to child containers
+    check_message(
+        "BaseExceptionGroup(BaseExceptionGroup(KeyboardInterrupt), ExceptionGroup(ValueError))",
+        RaisesGroup(RaisesGroup(KeyboardInterrupt), RaisesGroup(ValueError)),
+    )
+
+
+def test_assert_message() -> None:
+    # the message does not need to list all parameters to RaisesGroup, nor all exceptions
+    # in the exception group, as those are both visible in the traceback.
+    # first fails to match
+    with (
+        fails_raises_group("`TypeError()` is not an instance of `ValueError`"),
+        RaisesGroup(ValueError),
+    ):
+        raise ExceptionGroup("a", [TypeError()])
+    with (
+        fails_raises_group(
+            "Raised exception group did not match: \n"
+            "The following expected exceptions did not find a match:\n"
+            "  RaisesGroup(ValueError)\n"
+            "  RaisesGroup(ValueError, match='a')\n"
+            "The following raised exceptions did not find a match\n"
+            "  ExceptionGroup('', [RuntimeError()]):\n"
+            "    RaisesGroup(ValueError): `RuntimeError()` is not an instance of `ValueError`\n"
+            "    RaisesGroup(ValueError, match='a'): Regex pattern did not match the `ExceptionGroup()`.\n"
+            "     Regex: 'a'\n"
+            "     Input: ''\n"
+            "  RuntimeError():\n"
+            "    RaisesGroup(ValueError): `RuntimeError()` is not an exception group\n"
+            "    RaisesGroup(ValueError, match='a'): `RuntimeError()` is not an exception group",
+            add_prefix=False,  # to see the full structure
+        ),
+        RaisesGroup(RaisesGroup(ValueError), RaisesGroup(ValueError, match="a")),
+    ):
+        raise ExceptionGroup(
+            "",
+            [ExceptionGroup("", [RuntimeError()]), RuntimeError()],
+        )
+
+    with (
+        fails_raises_group(
+            "Raised exception group did not match: \n"
+            "2 matched exceptions. \n"
+            "The following expected exceptions did not find a match:\n"
+            "  RaisesGroup(RuntimeError)\n"
+            "  RaisesGroup(ValueError)\n"
+            "The following raised exceptions did not find a match\n"
+            "  RuntimeError():\n"
+            "    RaisesGroup(RuntimeError): `RuntimeError()` is not an exception group, but would match with `allow_unwrapped=True`\n"
+            "    RaisesGroup(ValueError): `RuntimeError()` is not an exception group\n"
+            "  ValueError('bar'):\n"
+            "    It matches `ValueError` which was paired with `ValueError('foo')`\n"
+            "    RaisesGroup(RuntimeError): `ValueError()` is not an exception group\n"
+            "    RaisesGroup(ValueError): `ValueError()` is not an exception group, but would match with `allow_unwrapped=True`",
+            add_prefix=False,  # to see the full structure
+        ),
+        RaisesGroup(
+            ValueError,
+            RaisesExc(TypeError),
+            RaisesGroup(RuntimeError),
+            RaisesGroup(ValueError),
+        ),
+    ):
+        raise ExceptionGroup(
+            "a",
+            [RuntimeError(), TypeError(), ValueError("foo"), ValueError("bar")],
+        )
+
+    with (
+        fails_raises_group(
+            "1 matched exception. `AssertionError()` is not an instance of `TypeError`"
+        ),
+        RaisesGroup(ValueError, TypeError),
+    ):
+        raise ExceptionGroup("a", [ValueError(), AssertionError()])
+
+    with (
+        fails_raises_group(
+            "RaisesExc(ValueError): `TypeError()` is not an instance of `ValueError`"
+        ),
+        RaisesGroup(RaisesExc(ValueError)),
+    ):
+        raise ExceptionGroup("a", [TypeError()])
+
+    # suggest escaping
+    with (
+        fails_raises_group(
+            # TODO: did not match Exceptiongroup('h(ell)o', ...) ?
+            "Raised exception group did not match: Regex pattern did not match the `ExceptionGroup()`.\n"
+            " Regex: 'h(ell)o'\n"
+            " Input: 'h(ell)o'\n"
+            " Did you mean to `re.escape()` the regex?",
+            add_prefix=False,  # to see the full structure
+        ),
+        RaisesGroup(ValueError, match="h(ell)o"),
+    ):
+        raise ExceptionGroup("h(ell)o", [ValueError()])
+    with (
+        fails_raises_group(
+            "RaisesExc(match='h(ell)o'): Regex pattern did not match.\n"
+            " Regex: 'h(ell)o'\n"
+            " Input: 'h(ell)o'\n"
+            " Did you mean to `re.escape()` the regex?",
+        ),
+        RaisesGroup(RaisesExc(match="h(ell)o")),
+    ):
+        raise ExceptionGroup("", [ValueError("h(ell)o")])
+
+    with (
+        fails_raises_group(
+            "Raised exception group did not match: \n"
+            "The following expected exceptions did not find a match:\n"
+            "  ValueError\n"
+            "  ValueError\n"
+            "  ValueError\n"
+            "  ValueError\n"
+            "The following raised exceptions did not find a match\n"
+            "  ExceptionGroup('', [ValueError(), TypeError()]):\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`",
+            add_prefix=False,  # to see the full structure
+        ),
+        RaisesGroup(ValueError, ValueError, ValueError, ValueError),
+    ):
+        raise ExceptionGroup("", [ExceptionGroup("", [ValueError(), TypeError()])])
+
+
+def test_message_indent() -> None:
+    with (
+        fails_raises_group(
+            "Raised exception group did not match: \n"
+            "The following expected exceptions did not find a match:\n"
+            "  RaisesGroup(ValueError, ValueError)\n"
+            "  ValueError\n"
+            "The following raised exceptions did not find a match\n"
+            "  ExceptionGroup('', [TypeError(), RuntimeError()]):\n"
+            "    RaisesGroup(ValueError, ValueError): \n"
+            "      The following expected exceptions did not find a match:\n"
+            "        ValueError\n"
+            "        ValueError\n"
+            "      The following raised exceptions did not find a match\n"
+            "        TypeError():\n"
+            "          `TypeError()` is not an instance of `ValueError`\n"
+            "          `TypeError()` is not an instance of `ValueError`\n"
+            "        RuntimeError():\n"
+            "          `RuntimeError()` is not an instance of `ValueError`\n"
+            "          `RuntimeError()` is not an instance of `ValueError`\n"
+            # TODO: this line is not great, should maybe follow the same format as the other and say
+            # ValueError: Unexpected nested `ExceptionGroup()` (?)
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "  TypeError():\n"
+            "    RaisesGroup(ValueError, ValueError): `TypeError()` is not an exception group\n"
+            "    `TypeError()` is not an instance of `ValueError`",
+            add_prefix=False,
+        ),
+        RaisesGroup(
+            RaisesGroup(ValueError, ValueError),
+            ValueError,
+        ),
+    ):
+        raise ExceptionGroup(
+            "",
+            [
+                ExceptionGroup("", [TypeError(), RuntimeError()]),
+                TypeError(),
+            ],
+        )
+    with (
+        fails_raises_group(
+            "Raised exception group did not match: \n"
+            "RaisesGroup(ValueError, ValueError): \n"
+            "  The following expected exceptions did not find a match:\n"
+            "    ValueError\n"
+            "    ValueError\n"
+            "  The following raised exceptions did not find a match\n"
+            "    TypeError():\n"
+            "      `TypeError()` is not an instance of `ValueError`\n"
+            "      `TypeError()` is not an instance of `ValueError`\n"
+            "    RuntimeError():\n"
+            "      `RuntimeError()` is not an instance of `ValueError`\n"
+            "      `RuntimeError()` is not an instance of `ValueError`",
+            add_prefix=False,
+        ),
+        RaisesGroup(
+            RaisesGroup(ValueError, ValueError),
+        ),
+    ):
+        raise ExceptionGroup(
+            "",
+            [
+                ExceptionGroup("", [TypeError(), RuntimeError()]),
+            ],
+        )
+
+
+def test_suggestion_on_nested_and_brief_error() -> None:
+    # Make sure "Did you mean" suggestion gets indented iff it follows a single-line error
+    with (
+        fails_raises_group(
+            "\n"
+            "The following expected exceptions did not find a match:\n"
+            "  RaisesGroup(ValueError)\n"
+            "  ValueError\n"
+            "The following raised exceptions did not find a match\n"
+            "  ExceptionGroup('', [ExceptionGroup('', [ValueError()])]):\n"
+            "    RaisesGroup(ValueError): Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "      Did you mean to use `flatten_subgroups=True`?\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`",
+        ),
+        RaisesGroup(RaisesGroup(ValueError), ValueError),
+    ):
+        raise ExceptionGroup(
+            "",
+            [ExceptionGroup("", [ExceptionGroup("", [ValueError()])])],
+        )
+    # if indented here it would look like another raised exception
+    with (
+        fails_raises_group(
+            "\n"
+            "The following expected exceptions did not find a match:\n"
+            "  RaisesGroup(ValueError, ValueError)\n"
+            "  ValueError\n"
+            "The following raised exceptions did not find a match\n"
+            "  ExceptionGroup('', [ValueError(), ExceptionGroup('', [ValueError()])]):\n"
+            "    RaisesGroup(ValueError, ValueError): \n"
+            "      1 matched exception. \n"
+            "      The following expected exceptions did not find a match:\n"
+            "        ValueError\n"
+            "          It matches `ValueError()` which was paired with `ValueError`\n"
+            "      The following raised exceptions did not find a match\n"
+            "        ExceptionGroup('', [ValueError()]):\n"
+            "          Unexpected nested `ExceptionGroup()`, expected `ValueError`\n"
+            "      Did you mean to use `flatten_subgroups=True`?\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`"
+        ),
+        RaisesGroup(RaisesGroup(ValueError, ValueError), ValueError),
+    ):
+        raise ExceptionGroup(
+            "",
+            [ExceptionGroup("", [ValueError(), ExceptionGroup("", [ValueError()])])],
+        )
+
+    # re.escape always comes after single-line errors
+    with (
+        fails_raises_group(
+            "\n"
+            "The following expected exceptions did not find a match:\n"
+            "  RaisesGroup(Exception, match='^hello')\n"
+            "  ValueError\n"
+            "The following raised exceptions did not find a match\n"
+            "  ExceptionGroup('^hello', [Exception()]):\n"
+            "    RaisesGroup(Exception, match='^hello'): Regex pattern did not match the `ExceptionGroup()`.\n"
+            "     Regex: '^hello'\n"
+            "     Input: '^hello'\n"
+            "     Did you mean to `re.escape()` the regex?\n"
+            "    Unexpected nested `ExceptionGroup()`, expected `ValueError`"
+        ),
+        RaisesGroup(RaisesGroup(Exception, match="^hello"), ValueError),
+    ):
+        raise ExceptionGroup("", [ExceptionGroup("^hello", [Exception()])])
+
+
+def test_assert_message_nested() -> None:
+    # we only get one instance of aaaaaaaaaa... and bbbbbb..., but we do get multiple instances of ccccc... and dddddd..
+    # but I think this now only prints the full repr when that is necessary to disambiguate exceptions
+    with (
+        fails_raises_group(
+            "Raised exception group did not match: \n"
+            "The following expected exceptions did not find a match:\n"
+            "  RaisesGroup(ValueError)\n"
+            "  RaisesGroup(RaisesGroup(ValueError))\n"
+            "  RaisesGroup(RaisesExc(TypeError, match='foo'))\n"
+            "  RaisesGroup(TypeError, ValueError)\n"
+            "The following raised exceptions did not find a match\n"
+            "  TypeError('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'):\n"
+            "    RaisesGroup(ValueError): `TypeError()` is not an exception group\n"
+            "    RaisesGroup(RaisesGroup(ValueError)): `TypeError()` is not an exception group\n"
+            "    RaisesGroup(RaisesExc(TypeError, match='foo')): `TypeError()` is not an exception group\n"
+            "    RaisesGroup(TypeError, ValueError): `TypeError()` is not an exception group\n"
+            "  ExceptionGroup('Exceptions from Trio nursery', [TypeError('bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb')]):\n"
+            "    RaisesGroup(ValueError): `TypeError()` is not an instance of `ValueError`\n"
+            "    RaisesGroup(RaisesGroup(ValueError)): RaisesGroup(ValueError): `TypeError()` is not an exception group\n"
+            "    RaisesGroup(RaisesExc(TypeError, match='foo')): RaisesExc(TypeError, match='foo'): Regex pattern did not match.\n"
+            "     Regex: 'foo'\n"
+            "     Input: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'\n"
+            "    RaisesGroup(TypeError, ValueError): 1 matched exception. Too few exceptions raised, found no match for: [ValueError]\n"
+            "  ExceptionGroup('Exceptions from Trio nursery', [TypeError('cccccccccccccccccccccccccccccc'), TypeError('dddddddddddddddddddddddddddddd')]):\n"
+            "    RaisesGroup(ValueError): \n"
+            "      The following expected exceptions did not find a match:\n"
+            "        ValueError\n"
+            "      The following raised exceptions did not find a match\n"
+            "        TypeError('cccccccccccccccccccccccccccccc'):\n"
+            "          `TypeError()` is not an instance of `ValueError`\n"
+            "        TypeError('dddddddddddddddddddddddddddddd'):\n"
+            "          `TypeError()` is not an instance of `ValueError`\n"
+            "    RaisesGroup(RaisesGroup(ValueError)): \n"
+            "      The following expected exceptions did not find a match:\n"
+            "        RaisesGroup(ValueError)\n"
+            "      The following raised exceptions did not find a match\n"
+            "        TypeError('cccccccccccccccccccccccccccccc'):\n"
+            "          RaisesGroup(ValueError): `TypeError()` is not an exception group\n"
+            "        TypeError('dddddddddddddddddddddddddddddd'):\n"
+            "          RaisesGroup(ValueError): `TypeError()` is not an exception group\n"
+            "    RaisesGroup(RaisesExc(TypeError, match='foo')): \n"
+            "      The following expected exceptions did not find a match:\n"
+            "        RaisesExc(TypeError, match='foo')\n"
+            "      The following raised exceptions did not find a match\n"
+            "        TypeError('cccccccccccccccccccccccccccccc'):\n"
+            "          RaisesExc(TypeError, match='foo'): Regex pattern did not match.\n"
+            "           Regex: 'foo'\n"
+            "           Input: 'cccccccccccccccccccccccccccccc'\n"
+            "        TypeError('dddddddddddddddddddddddddddddd'):\n"
+            "          RaisesExc(TypeError, match='foo'): Regex pattern did not match.\n"
+            "           Regex: 'foo'\n"
+            "           Input: 'dddddddddddddddddddddddddddddd'\n"
+            "    RaisesGroup(TypeError, ValueError): \n"
+            "      1 matched exception. \n"
+            "      The following expected exceptions did not find a match:\n"
+            "        ValueError\n"
+            "      The following raised exceptions did not find a match\n"
+            "        TypeError('dddddddddddddddddddddddddddddd'):\n"
+            "          It matches `TypeError` which was paired with `TypeError('cccccccccccccccccccccccccccccc')`\n"
+            "          `TypeError()` is not an instance of `ValueError`",
+            add_prefix=False,  # to see the full structure
+        ),
+        RaisesGroup(
+            RaisesGroup(ValueError),
+            RaisesGroup(RaisesGroup(ValueError)),
+            RaisesGroup(RaisesExc(TypeError, match="foo")),
+            RaisesGroup(TypeError, ValueError),
+        ),
+    ):
+        raise ExceptionGroup(
+            "",
+            [
+                TypeError("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
+                ExceptionGroup(
+                    "Exceptions from Trio nursery",
+                    [TypeError("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")],
+                ),
+                ExceptionGroup(
+                    "Exceptions from Trio nursery",
+                    [
+                        TypeError("cccccccccccccccccccccccccccccc"),
+                        TypeError("dddddddddddddddddddddddddddddd"),
+                    ],
+                ),
+            ],
+        )
+
+
+# CI always runs with hypothesis, but this is not a critical test - it overlaps
+# with several others
+@pytest.mark.skipif(
+    "hypothesis" in sys.modules,
+    reason="hypothesis may have monkeypatched _check_repr",
+)
+def test_check_no_patched_repr() -> None:  # pragma: no cover
+    # We make `_check_repr` monkeypatchable to avoid this very ugly and verbose
+    # repr. The other tests that use `check` make use of `_check_repr` so they'll
+    # continue passing in case it is patched - but we have this one test that
+    # demonstrates just how nasty it gets otherwise.
+    match_str = (
+        r"^Raised exception group did not match: \n"
+        r"The following expected exceptions did not find a match:\n"
+        r"  RaisesExc\(check=<function test_check_no_patched_repr.<locals>.<lambda> at .*>\)\n"
+        r"  TypeError\n"
+        r"The following raised exceptions did not find a match\n"
+        r"  ValueError\('foo'\):\n"
+        r"    RaisesExc\(check=<function test_check_no_patched_repr.<locals>.<lambda> at .*>\): check did not return True\n"
+        r"    `ValueError\(\)` is not an instance of `TypeError`\n"
+        r"  ValueError\('bar'\):\n"
+        r"    RaisesExc\(check=<function test_check_no_patched_repr.<locals>.<lambda> at .*>\): check did not return True\n"
+        r"    `ValueError\(\)` is not an instance of `TypeError`$"
+    )
+    with (
+        pytest.raises(Failed, match=match_str),
+        RaisesGroup(RaisesExc(check=lambda x: False), TypeError),
+    ):
+        raise ExceptionGroup("", [ValueError("foo"), ValueError("bar")])
+
+
+def test_misordering_example() -> None:
+    with (
+        fails_raises_group(
+            "\n"
+            "3 matched exceptions. \n"
+            "The following expected exceptions did not find a match:\n"
+            "  RaisesExc(ValueError, match='foo')\n"
+            "    It matches `ValueError('foo')` which was paired with `ValueError`\n"
+            "    It matches `ValueError('foo')` which was paired with `ValueError`\n"
+            "    It matches `ValueError('foo')` which was paired with `ValueError`\n"
+            "The following raised exceptions did not find a match\n"
+            "  ValueError('bar'):\n"
+            "    It matches `ValueError` which was paired with `ValueError('foo')`\n"
+            "    It matches `ValueError` which was paired with `ValueError('foo')`\n"
+            "    It matches `ValueError` which was paired with `ValueError('foo')`\n"
+            "    RaisesExc(ValueError, match='foo'): Regex pattern did not match.\n"
+            "     Regex: 'foo'\n"
+            "     Input: 'bar'\n"
+            "There exist a possible match when attempting an exhaustive check, but RaisesGroup uses a greedy algorithm. Please make your expected exceptions more stringent with `RaisesExc` etc so the greedy algorithm can function."
+        ),
+        RaisesGroup(
+            ValueError, ValueError, ValueError, RaisesExc(ValueError, match="foo")
+        ),
+    ):
+        raise ExceptionGroup(
+            "",
+            [
+                ValueError("foo"),
+                ValueError("foo"),
+                ValueError("foo"),
+                ValueError("bar"),
+            ],
+        )
+
+
+def test_brief_error_on_one_fail() -> None:
+    """If only one raised and one expected fail to match up, we print a full table iff
+    the raised exception would match one of the expected that previously got matched"""
+    # no also-matched
+    with (
+        fails_raises_group(
+            "1 matched exception. `TypeError()` is not an instance of `RuntimeError`"
+        ),
+        RaisesGroup(ValueError, RuntimeError),
+    ):
+        raise ExceptionGroup("", [ValueError(), TypeError()])
+
+    # raised would match an expected
+    with (
+        fails_raises_group(
+            "\n"
+            "1 matched exception. \n"
+            "The following expected exceptions did not find a match:\n"
+            "  RuntimeError\n"
+            "The following raised exceptions did not find a match\n"
+            "  TypeError():\n"
+            "    It matches `Exception` which was paired with `ValueError()`\n"
+            "    `TypeError()` is not an instance of `RuntimeError`"
+        ),
+        RaisesGroup(Exception, RuntimeError),
+    ):
+        raise ExceptionGroup("", [ValueError(), TypeError()])
+
+    # expected would match a raised
+    with (
+        fails_raises_group(
+            "\n"
+            "1 matched exception. \n"
+            "The following expected exceptions did not find a match:\n"
+            "  ValueError\n"
+            "    It matches `ValueError()` which was paired with `ValueError`\n"
+            "The following raised exceptions did not find a match\n"
+            "  TypeError():\n"
+            "    `TypeError()` is not an instance of `ValueError`"
+        ),
+        RaisesGroup(ValueError, ValueError),
+    ):
+        raise ExceptionGroup("", [ValueError(), TypeError()])
+
+
+def test_identity_oopsies() -> None:
+    # it's both possible to have several instances of the same exception in the same group
+    # and to expect multiple of the same type
+    # this previously messed up the logic
+
+    with (
+        fails_raises_group(
+            "3 matched exceptions. `RuntimeError()` is not an instance of `TypeError`"
+        ),
+        RaisesGroup(ValueError, ValueError, ValueError, TypeError),
+    ):
+        raise ExceptionGroup(
+            "", [ValueError(), ValueError(), ValueError(), RuntimeError()]
+        )
+
+    e = ValueError("foo")
+    m = RaisesExc(match="bar")
+    with (
+        fails_raises_group(
+            "\n"
+            "The following expected exceptions did not find a match:\n"
+            "  RaisesExc(match='bar')\n"
+            "  RaisesExc(match='bar')\n"
+            "  RaisesExc(match='bar')\n"
+            "The following raised exceptions did not find a match\n"
+            "  ValueError('foo'):\n"
+            "    RaisesExc(match='bar'): Regex pattern did not match.\n"
+            "     Regex: 'bar'\n"
+            "     Input: 'foo'\n"
+            "    RaisesExc(match='bar'): Regex pattern did not match.\n"
+            "     Regex: 'bar'\n"
+            "     Input: 'foo'\n"
+            "    RaisesExc(match='bar'): Regex pattern did not match.\n"
+            "     Regex: 'bar'\n"
+            "     Input: 'foo'\n"
+            "  ValueError('foo'):\n"
+            "    RaisesExc(match='bar'): Regex pattern did not match.\n"
+            "     Regex: 'bar'\n"
+            "     Input: 'foo'\n"
+            "    RaisesExc(match='bar'): Regex pattern did not match.\n"
+            "     Regex: 'bar'\n"
+            "     Input: 'foo'\n"
+            "    RaisesExc(match='bar'): Regex pattern did not match.\n"
+            "     Regex: 'bar'\n"
+            "     Input: 'foo'\n"
+            "  ValueError('foo'):\n"
+            "    RaisesExc(match='bar'): Regex pattern did not match.\n"
+            "     Regex: 'bar'\n"
+            "     Input: 'foo'\n"
+            "    RaisesExc(match='bar'): Regex pattern did not match.\n"
+            "     Regex: 'bar'\n"
+            "     Input: 'foo'\n"
+            "    RaisesExc(match='bar'): Regex pattern did not match.\n"
+            "     Regex: 'bar'\n"
+            "     Input: 'foo'"
+        ),
+        RaisesGroup(m, m, m),
+    ):
+        raise ExceptionGroup("", [e, e, e])
+
+
+def test_raisesexc() -> None:
+    with pytest.raises(
+        ValueError,
+        match=r"^You must specify at least one parameter to match on.$",
+    ):
+        RaisesExc()  # type: ignore[call-overload]
+    with pytest.raises(
+        ValueError,
+        match=wrap_escape(
+            "expected exception must be a BaseException type, not 'object'"
+        ),
+    ):
+        RaisesExc(object)  # type: ignore[type-var]
+
+    with RaisesGroup(RaisesExc(ValueError)):
+        raise ExceptionGroup("", (ValueError(),))
+    with (
+        fails_raises_group(
+            "RaisesExc(TypeError): `ValueError()` is not an instance of `TypeError`"
+        ),
+        RaisesGroup(RaisesExc(TypeError)),
+    ):
+        raise ExceptionGroup("", (ValueError(),))
+
+    with RaisesExc(ValueError):
+        raise ValueError
+
+    # FIXME: leaving this one formatted differently for now to not change
+    # tests in python/raises.py
+    with pytest.raises(Failed, match=wrap_escape("DID NOT RAISE <class 'ValueError'>")):
+        with RaisesExc(ValueError):
+            ...
+
+    with pytest.raises(Failed, match=wrap_escape("DID NOT RAISE any exception")):
+        with RaisesExc(match="foo"):
+            ...
+
+    with pytest.raises(
+        # FIXME: do we want repr(type) or type.__name__ ?
+        Failed,
+        match=wrap_escape(
+            "DID NOT RAISE any of (<class 'ValueError'>, <class 'TypeError'>)"
+        ),
+    ):
+        with RaisesExc((ValueError, TypeError)):
+            ...
+
+    # currently RaisesGroup says "Raised exception did not match" but RaisesExc doesn't...
+    with pytest.raises(
+        AssertionError,
+        match=wrap_escape("Regex pattern did not match.\n Regex: 'foo'\n Input: 'bar'"),
+    ):
+        with RaisesExc(TypeError, match="foo"):
+            raise TypeError("bar")
+
+
+def test_raisesexc_match() -> None:
+    with RaisesGroup(RaisesExc(ValueError, match="foo")):
+        raise ExceptionGroup("", (ValueError("foo"),))
+    with (
+        fails_raises_group(
+            "RaisesExc(ValueError, match='foo'): Regex pattern did not match.\n"
+            " Regex: 'foo'\n"
+            " Input: 'bar'"
+        ),
+        RaisesGroup(RaisesExc(ValueError, match="foo")),
+    ):
+        raise ExceptionGroup("", (ValueError("bar"),))
+
+    # Can be used without specifying the type
+    with RaisesGroup(RaisesExc(match="foo")):
+        raise ExceptionGroup("", (ValueError("foo"),))
+    with (
+        fails_raises_group(
+            "RaisesExc(match='foo'): Regex pattern did not match.\n"
+            " Regex: 'foo'\n"
+            " Input: 'bar'"
+        ),
+        RaisesGroup(RaisesExc(match="foo")),
+    ):
+        raise ExceptionGroup("", (ValueError("bar"),))
+
+    # check ^$
+    with RaisesGroup(RaisesExc(ValueError, match="^bar$")):
+        raise ExceptionGroup("", [ValueError("bar")])
+    with (
+        fails_raises_group(
+            "\nRaisesExc(ValueError, match='^bar$'): \n  - barr\n  ?    -\n  + bar"
+        ),
+        RaisesGroup(RaisesExc(ValueError, match="^bar$")),
+    ):
+        raise ExceptionGroup("", [ValueError("barr")])
+
+
+def test_RaisesExc_check() -> None:
+    def check_oserror_and_errno_is_5(e: BaseException) -> bool:
+        return isinstance(e, OSError) and e.errno == 5
+
+    with RaisesGroup(RaisesExc(check=check_oserror_and_errno_is_5)):
+        raise ExceptionGroup("", (OSError(5, ""),))
+
+    # specifying exception_type narrows the parameter type to the callable
+    def check_errno_is_5(e: OSError) -> bool:
+        return e.errno == 5
+
+    with RaisesGroup(RaisesExc(OSError, check=check_errno_is_5)):
+        raise ExceptionGroup("", (OSError(5, ""),))
+
+    # avoid printing overly verbose repr multiple times
+    with (
+        fails_raises_group(
+            f"RaisesExc(OSError, check={check_errno_is_5!r}): check did not return True"
+        ),
+        RaisesGroup(RaisesExc(OSError, check=check_errno_is_5)),
+    ):
+        raise ExceptionGroup("", (OSError(6, ""),))
+
+    # in nested cases you still get it multiple times though
+    # to address this you'd need logic in RaisesExc.__repr__ and RaisesGroup.__repr__
+    with (
+        fails_raises_group(
+            f"RaisesGroup(RaisesExc(OSError, check={check_errno_is_5!r})): RaisesExc(OSError, check={check_errno_is_5!r}): check did not return True"
+        ),
+        RaisesGroup(RaisesGroup(RaisesExc(OSError, check=check_errno_is_5))),
+    ):
+        raise ExceptionGroup("", [ExceptionGroup("", [OSError(6, "")])])
+
+
+def test_raisesexc_tostring() -> None:
+    assert str(RaisesExc(ValueError)) == "RaisesExc(ValueError)"
+    assert str(RaisesExc(match="[a-z]")) == "RaisesExc(match='[a-z]')"
+    pattern_no_flags = re.compile(r"noflag", 0)
+    assert str(RaisesExc(match=pattern_no_flags)) == "RaisesExc(match='noflag')"
+    pattern_flags = re.compile(r"noflag", re.IGNORECASE)
+    assert str(RaisesExc(match=pattern_flags)) == f"RaisesExc(match={pattern_flags!r})"
+    assert (
+        str(RaisesExc(ValueError, match="re", check=bool))
+        == f"RaisesExc(ValueError, match='re', check={bool!r})"
+    )
+
+
+def test_raisesgroup_tostring() -> None:
+    def check_str_and_repr(s: str) -> None:
+        evaled = eval(s)
+        assert s == str(evaled) == repr(evaled)
+
+    check_str_and_repr("RaisesGroup(ValueError)")
+    check_str_and_repr("RaisesGroup(RaisesGroup(ValueError))")
+    check_str_and_repr("RaisesGroup(RaisesExc(ValueError))")
+    check_str_and_repr("RaisesGroup(ValueError, allow_unwrapped=True)")
+    check_str_and_repr("RaisesGroup(ValueError, match='aoeu')")
+
+    assert (
+        str(RaisesGroup(ValueError, match="[a-z]", check=bool))
+        == f"RaisesGroup(ValueError, match='[a-z]', check={bool!r})"
+    )
+
+
+def test_assert_matches() -> None:
+    e = ValueError()
+
+    # it's easy to do this
+    assert RaisesExc(ValueError).matches(e)
+
+    # but you don't get a helpful error
+    with pytest.raises(AssertionError, match=r"assert False\n \+  where False = .*"):
+        assert RaisesExc(TypeError).matches(e)
+
+    with pytest.raises(
+        AssertionError,
+        match=wrap_escape(
+            "`ValueError()` is not an instance of `TypeError`\n"
+            "assert False\n"
+            " +  where False = matches(ValueError())\n"
+            " +    where matches = RaisesExc(TypeError).matches"
+        ),
+    ):
+        # you'd need to do this arcane incantation
+        assert (m := RaisesExc(TypeError)).matches(e), m.fail_reason
+
+    # but even if we add assert_matches, will people remember to use it?
+    # other than writing a linter rule, I don't think we can catch `assert RaisesExc(...).matches`
+    # ... no wait pytest catches other asserts ... so we probably can??
+
+
+# https://github.com/pytest-dev/pytest/issues/12504
+def test_xfail_raisesgroup(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+        import sys
+        import pytest
+        if sys.version_info < (3, 11):
+            from exceptiongroup import ExceptionGroup
+        @pytest.mark.xfail(raises=pytest.RaisesGroup(ValueError))
+        def test_foo() -> None:
+            raise ExceptionGroup("foo", [ValueError()])
+        """
+    )
+    result = pytester.runpytest()
+    result.assert_outcomes(xfailed=1)
+
+
+def test_xfail_RaisesExc(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+        import pytest
+        @pytest.mark.xfail(raises=pytest.RaisesExc(ValueError))
+        def test_foo() -> None:
+            raise ValueError
+        """
+    )
+    result = pytester.runpytest()
+    result.assert_outcomes(xfailed=1)
+
+
+@pytest.mark.parametrize(
+    "wrap_in_group,handler",
+    [
+        (False, pytest.raises(ValueError)),
+        (True, RaisesGroup(ValueError)),
+    ],
+)
+def test_parametrizing_conditional_raisesgroup(
+    wrap_in_group: bool, handler: AbstractContextManager[ExceptionInfo[BaseException]]
+) -> None:
+    with handler:
+        if wrap_in_group:
+            raise ExceptionGroup("", [ValueError()])
+        raise ValueError()
+
+
+def test_annotated_group() -> None:
+    # repr depends on if exceptiongroup backport is being used or not
+    t = repr(ExceptionGroup[ValueError])
+    msg = "Only `ExceptionGroup[Exception]` or `BaseExceptionGroup[BaseExeption]` are accepted as generic types but got `{}`. As `raises` will catch all instances of the specified group regardless of the generic argument specific nested exceptions has to be checked with `RaisesGroup`."
+
+    fail_msg = wrap_escape(msg.format(t))
+    with pytest.raises(ValueError, match=fail_msg):
+        RaisesGroup(ExceptionGroup[ValueError])
+    with pytest.raises(ValueError, match=fail_msg):
+        RaisesExc(ExceptionGroup[ValueError])
+    with pytest.raises(
+        ValueError,
+        match=wrap_escape(msg.format(repr(BaseExceptionGroup[KeyboardInterrupt]))),
+    ):
+        with RaisesExc(BaseExceptionGroup[KeyboardInterrupt]):
+            raise BaseExceptionGroup("", [KeyboardInterrupt()])
+
+    with RaisesGroup(ExceptionGroup[Exception]):
+        raise ExceptionGroup(
+            "", [ExceptionGroup("", [ValueError(), ValueError(), ValueError()])]
+        )
+    with RaisesExc(BaseExceptionGroup[BaseException]):
+        raise BaseExceptionGroup("", [KeyboardInterrupt()])
+
+
+def test_tuples() -> None:
+    # raises has historically supported one of several exceptions being raised
+    with pytest.raises((ValueError, IndexError)):
+        raise ValueError
+    # so now RaisesExc also does
+    with RaisesExc((ValueError, IndexError)):
+        raise IndexError
+    # but RaisesGroup currently doesn't. There's an argument it shouldn't because
+    # it can be confusing - RaisesGroup((ValueError, TypeError)) looks a lot like
+    # RaisesGroup(ValueError, TypeError), and the former might be interpreted as the latter.
+    with pytest.raises(
+        TypeError,
+        match=wrap_escape(
+            "expected exception must be a BaseException type, RaisesExc, or RaisesGroup, not 'tuple'.\n"
+            "RaisesGroup does not support tuples of exception types when expecting one of "
+            "several possible exception types like RaisesExc.\n"
+            "If you meant to expect a group with multiple exceptions, list them as separate arguments."
+        ),
+    ):
+        RaisesGroup((ValueError, IndexError))  # type: ignore[call-overload]
diff --git a/testing/python/show_fixtures_per_test.py b/testing/python/show_fixtures_per_test.py
index f756dca41c7..c860b61e21b 100644
--- a/testing/python/show_fixtures_per_test.py
+++ b/testing/python/show_fixtures_per_test.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from _pytest.pytester import Pytester
 
 
diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py
index 8c10e230b0c..5d1513b6206 100644
--- a/testing/test_argcomplete.py
+++ b/testing/test_argcomplete.py
@@ -1,9 +1,13 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from pathlib import Path
 import subprocess
 import sys
-from pathlib import Path
 
-import pytest
 from _pytest.monkeypatch import MonkeyPatch
+import pytest
+
 
 # Test for _argcomplete but not specific for any application.
 
diff --git a/testing/test_assertion.py b/testing/test_assertion.py
index e8717590d53..2c2830eb929 100644
--- a/testing/test_assertion.py
+++ b/testing/test_assertion.py
@@ -1,32 +1,81 @@
-import collections
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import MutableSequence
 import sys
 import textwrap
 from typing import Any
-from typing import List
-from typing import MutableSequence
-from typing import Optional
+from typing import NamedTuple
 
 import attr
 
-import _pytest.assertion as plugin
-import pytest
 from _pytest import outcomes
+import _pytest.assertion as plugin
 from _pytest.assertion import truncate
 from _pytest.assertion import util
+from _pytest.config import Config as _Config
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
+import pytest
 
 
-def mock_config(verbose=0):
+def mock_config(verbose: int = 0, assertion_override: int | None = None):
+    class TerminalWriter:
+        def _highlight(self, source, lexer="python"):
+            return source
+
     class Config:
-        def getoption(self, name):
-            if name == "verbose":
+        def get_terminal_writer(self):
+            return TerminalWriter()
+
+        def get_verbosity(self, verbosity_type: str | None = None) -> int:
+            if verbosity_type is None:
                 return verbose
-            raise KeyError("Not mocked out: %s" % name)
+            if verbosity_type == _Config.VERBOSITY_ASSERTIONS:
+                if assertion_override is not None:
+                    return assertion_override
+                return verbose
+
+            raise KeyError(f"Not mocked out: {verbosity_type}")
 
     return Config()
 
 
+class TestMockConfig:
+    SOME_VERBOSITY_LEVEL = 3
+    SOME_OTHER_VERBOSITY_LEVEL = 10
+
+    def test_verbose_exposes_value(self):
+        config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL)
+
+        assert config.get_verbosity() == TestMockConfig.SOME_VERBOSITY_LEVEL
+
+    def test_get_assertion_override_not_set_verbose_value(self):
+        config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL)
+
+        assert (
+            config.get_verbosity(_Config.VERBOSITY_ASSERTIONS)
+            == TestMockConfig.SOME_VERBOSITY_LEVEL
+        )
+
+    def test_get_assertion_override_set_custom_value(self):
+        config = mock_config(
+            verbose=TestMockConfig.SOME_VERBOSITY_LEVEL,
+            assertion_override=TestMockConfig.SOME_OTHER_VERBOSITY_LEVEL,
+        )
+
+        assert (
+            config.get_verbosity(_Config.VERBOSITY_ASSERTIONS)
+            == TestMockConfig.SOME_OTHER_VERBOSITY_LEVEL
+        )
+
+    def test_get_unsupported_type_error(self):
+        config = mock_config(verbose=TestMockConfig.SOME_VERBOSITY_LEVEL)
+
+        with pytest.raises(KeyError):
+            config.get_verbosity("--- NOT A VERBOSITY LEVEL ---")
+
+
 class TestImportHookInstallation:
     @pytest.mark.parametrize("initial_conftest", [True, False])
     @pytest.mark.parametrize("mode", ["plain", "rewrite"])
@@ -52,7 +101,7 @@ def test(check_first):
             """,
         }
         pytester.makepyfile(**contents)
-        result = pytester.runpytest_subprocess("--assert=%s" % mode)
+        result = pytester.runpytest_subprocess(f"--assert={mode}")
         if mode == "plain":
             expected = "E       AssertionError"
         elif mode == "rewrite":
@@ -83,7 +132,7 @@ def test_dummy_failure(pytester):  # how meta!
                 "E       assert {'failed': 1,... 'skipped': 0} == {'failed': 0,... 'skipped': 0}",
                 "E         Omitting 1 identical items, use -vv to show",
                 "E         Differing items:",
-                "E         Use -v to get the full diff",
+                "E         Use -v to get more diff",
             ]
         )
         # XXX: unstable output.
@@ -114,7 +163,7 @@ def test_foo(check_first):
             """,
         }
         pytester.makepyfile(**contents)
-        result = pytester.runpytest_subprocess("--assert=%s" % mode)
+        result = pytester.runpytest_subprocess(f"--assert={mode}")
         if mode == "plain":
             expected = "E       AssertionError"
         elif mode == "rewrite":
@@ -133,11 +182,9 @@ def test_pytest_plugins_rewrite_module_names(
         """
         plugins = '"ham"' if mode == "str" else '["ham"]'
         contents = {
-            "conftest.py": """
+            "conftest.py": f"""
                 pytest_plugins = {plugins}
-            """.format(
-                plugins=plugins
-            ),
+            """,
             "ham.py": """
                 import pytest
             """,
@@ -171,12 +218,38 @@ def test_foo(pytestconfig):
         assert result.ret == 0
 
     @pytest.mark.parametrize("mode", ["plain", "rewrite"])
+    @pytest.mark.parametrize("disable_plugin_autoload", ["env_var", "cli", ""])
+    @pytest.mark.parametrize("explicit_specify", ["env_var", "cli", ""])
     def test_installed_plugin_rewrite(
-        self, pytester: Pytester, mode, monkeypatch
+        self,
+        pytester: Pytester,
+        mode: str,
+        monkeypatch: pytest.MonkeyPatch,
+        disable_plugin_autoload: str,
+        explicit_specify: str,
     ) -> None:
-        monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
+        args = ["mainwrapper.py", "-s", f"--assert={mode}"]
+        if disable_plugin_autoload == "env_var":
+            monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
+        elif disable_plugin_autoload == "cli":
+            monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
+            args.append("--disable-plugin-autoload")
+        else:
+            assert disable_plugin_autoload == ""
+            monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
+
+        name = "spamplugin"
+
+        if explicit_specify == "env_var":
+            monkeypatch.setenv("PYTEST_PLUGINS", name)
+        elif explicit_specify == "cli":
+            args.append("-p")
+            args.append(name)
+        else:
+            assert explicit_specify == ""
+
         # Make sure the hook is installed early enough so that plugins
-        # installed via setuptools are rewritten.
+        # installed via distribution package are rewritten.
         pytester.mkdir("hampkg")
         contents = {
             "hampkg/__init__.py": """\
@@ -199,11 +272,11 @@ def check(values, value):
                 return check
             """,
             "mainwrapper.py": """\
+            import importlib.metadata
             import pytest
-            from _pytest.compat import importlib_metadata
 
             class DummyEntryPoint(object):
-                name = 'spam'
+                name = 'spamplugin'
                 module_name = 'spam.py'
                 group = 'pytest11'
 
@@ -220,7 +293,7 @@ class DummyDistInfo(object):
             def distributions():
                 return (DummyDistInfo(),)
 
-            importlib_metadata.distributions = distributions
+            importlib.metadata.distributions = distributions
             pytest.main()
             """,
             "test_foo.py": """\
@@ -228,20 +301,29 @@ def test(check_first):
                 check_first([10, 30], 30)
 
             def test2(check_first2):
-                check_first([10, 30], 30)
+                check_first2([10, 30], 30)
             """,
         }
         pytester.makepyfile(**contents)
-        result = pytester.run(
-            sys.executable, "mainwrapper.py", "-s", "--assert=%s" % mode
-        )
+        result = pytester.run(sys.executable, *args)
         if mode == "plain":
             expected = "E       AssertionError"
         elif mode == "rewrite":
             expected = "*assert 10 == 30*"
         else:
             assert 0
-        result.stdout.fnmatch_lines([expected])
+
+        if not disable_plugin_autoload or explicit_specify:
+            result.assert_outcomes(failed=2)
+            result.stdout.fnmatch_lines([expected, expected])
+        else:
+            result.assert_outcomes(errors=2)
+            result.stdout.fnmatch_lines(
+                [
+                    "E       fixture 'check_first' not found",
+                    "E       fixture 'check_first2' not found",
+                ]
+            )
 
     def test_rewrite_ast(self, pytester: Pytester) -> None:
         pytester.mkdir("pkg")
@@ -322,12 +404,12 @@ def test_check(list):
         result.stdout.fnmatch_lines(["*test_hello*FAIL*", "*test_check*PASS*"])
 
 
-def callop(op: str, left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]:
+def callop(op: str, left: Any, right: Any, verbose: int = 0) -> list[str] | None:
     config = mock_config(verbose=verbose)
     return plugin.pytest_assertrepr_compare(config, op, left, right)
 
 
-def callequal(left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]:
+def callequal(left: Any, right: Any, verbose: int = 0) -> list[str] | None:
     return callop("==", left, right, verbose)
 
 
@@ -344,6 +426,7 @@ def test_summary(self) -> None:
     def test_text_diff(self) -> None:
         assert callequal("spam", "eggs") == [
             "'spam' == 'eggs'",
+            "",
             "- eggs",
             "+ spam",
         ]
@@ -351,7 +434,7 @@ def test_text_diff(self) -> None:
     def test_text_skipping(self) -> None:
         lines = callequal("a" * 50 + "spam", "a" * 50 + "eggs")
         assert lines is not None
-        assert "Skipping" in lines[1]
+        assert "Skipping" in lines[2]
         for line in lines:
             assert "a" * 50 not in line
 
@@ -375,8 +458,9 @@ def test_bytes_diff_normal(self) -> None:
 
         assert diff == [
             "b'spam' == b'eggs'",
+            "",
             "At index 0 diff: b's' != b'e'",
-            "Use -v to get the full diff",
+            "Use -v to get more diff",
         ]
 
     def test_bytes_diff_verbose(self) -> None:
@@ -384,7 +468,9 @@ def test_bytes_diff_verbose(self) -> None:
         diff = callequal(b"spam", b"eggs", verbose=1)
         assert diff == [
             "b'spam' == b'eggs'",
+            "",
             "At index 0 diff: b's' != b'e'",
+            "",
             "Full diff:",
             "- b'eggs'",
             "+ b'spam'",
@@ -403,11 +489,14 @@ def test_list(self) -> None:
                 [0, 2],
                 """
                 Full diff:
-                - [0, 2]
+                  [
+                      0,
+                -     2,
                 ?     ^
-                + [0, 1]
+                +     1,
                 ?     ^
-            """,
+                  ]
+                """,
                 id="lists",
             ),
             pytest.param(
@@ -415,10 +504,12 @@ def test_list(self) -> None:
                 {0: 2},
                 """
                 Full diff:
-                - {0: 2}
-                ?     ^
-                + {0: 1}
-                ?     ^
+                  {
+                -     0: 2,
+                ?        ^
+                +     0: 1,
+                ?        ^
+                  }
             """,
                 id="dicts",
             ),
@@ -427,10 +518,13 @@ def test_list(self) -> None:
                 {0, 2},
                 """
                 Full diff:
-                - {0, 2}
+                  {
+                      0,
+                -     2,
                 ?     ^
-                + {0, 1}
+                +     1,
                 ?     ^
+                  }
             """,
                 id="sets",
             ),
@@ -444,11 +538,20 @@ def test_iterable_full_diff(self, left, right, expected) -> None:
         """
         expl = callequal(left, right, verbose=0)
         assert expl is not None
-        assert expl[-1] == "Use -v to get the full diff"
+        assert expl[-1] == "Use -v to get more diff"
         verbose_expl = callequal(left, right, verbose=1)
         assert verbose_expl is not None
         assert "\n".join(verbose_expl).endswith(textwrap.dedent(expected).strip())
 
+    def test_iterable_quiet(self) -> None:
+        expl = callequal([1, 2], [10, 2], verbose=-1)
+        assert expl == [
+            "[1, 2] == [10, 2]",
+            "",
+            "At index 0 diff: 1 != 10",
+            "Use -v to get more diff",
+        ]
+
     def test_iterable_full_diff_ci(
         self, monkeypatch: MonkeyPatch, pytester: Pytester
     ) -> None:
@@ -466,7 +569,7 @@ def test_full_diff():
 
         monkeypatch.delenv("CI", raising=False)
         result = pytester.runpytest()
-        result.stdout.fnmatch_lines(["E         Use -v to get the full diff"])
+        result.stdout.fnmatch_lines(["E         Use -v to get more diff"])
 
     def test_list_different_lengths(self) -> None:
         expl = callequal([0, 1], [0, 1, 2])
@@ -483,26 +586,30 @@ def test_list_wrap_for_multiple_lines(self) -> None:
         diff = callequal(l1, l2, verbose=True)
         assert diff == [
             "['a', 'b', 'c'] == ['a', 'b', 'c...dddddddddddd']",
+            "",
             "Right contains one more item: '" + long_d + "'",
+            "",
             "Full diff:",
             "  [",
-            "   'a',",
-            "   'b',",
-            "   'c',",
-            "-  '" + long_d + "',",
+            "      'a',",
+            "      'b',",
+            "      'c',",
+            "-     '" + long_d + "',",
             "  ]",
         ]
 
         diff = callequal(l2, l1, verbose=True)
         assert diff == [
             "['a', 'b', 'c...dddddddddddd'] == ['a', 'b', 'c']",
+            "",
             "Left contains one more item: '" + long_d + "'",
+            "",
             "Full diff:",
             "  [",
-            "   'a',",
-            "   'b',",
-            "   'c',",
-            "+  '" + long_d + "',",
+            "      'a',",
+            "      'b',",
+            "      'c',",
+            "+     '" + long_d + "',",
             "  ]",
         ]
 
@@ -515,36 +622,40 @@ def test_list_wrap_for_width_rewrap_same_length(self) -> None:
         diff = callequal(l1, l2, verbose=True)
         assert diff == [
             "['aaaaaaaaaaa...cccccccccccc'] == ['bbbbbbbbbbb...aaaaaaaaaaaa']",
+            "",
             "At index 0 diff: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' != 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'",
+            "",
             "Full diff:",
             "  [",
-            "+  'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',",
-            "   'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',",
-            "   'cccccccccccccccccccccccccccccc',",
-            "-  'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',",
+            "+     'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',",
+            "      'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',",
+            "      'cccccccccccccccccccccccccccccc',",
+            "-     'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',",
             "  ]",
         ]
 
     def test_list_dont_wrap_strings(self) -> None:
         long_a = "a" * 10
-        l1 = ["a"] + [long_a for _ in range(0, 7)]
+        l1 = ["a"] + [long_a for _ in range(7)]
         l2 = ["should not get wrapped"]
         diff = callequal(l1, l2, verbose=True)
         assert diff == [
             "['a', 'aaaaaa...aaaaaaa', ...] == ['should not get wrapped']",
+            "",
             "At index 0 diff: 'a' != 'should not get wrapped'",
             "Left contains 7 more items, first extra item: 'aaaaaaaaaa'",
+            "",
             "Full diff:",
             "  [",
-            "-  'should not get wrapped',",
-            "+  'a',",
-            "+  'aaaaaaaaaa',",
-            "+  'aaaaaaaaaa',",
-            "+  'aaaaaaaaaa',",
-            "+  'aaaaaaaaaa',",
-            "+  'aaaaaaaaaa',",
-            "+  'aaaaaaaaaa',",
-            "+  'aaaaaaaaaa',",
+            "-     'should not get wrapped',",
+            "+     'a',",
+            "+     'aaaaaaaaaa',",
+            "+     'aaaaaaaaaa',",
+            "+     'aaaaaaaaaa',",
+            "+     'aaaaaaaaaa',",
+            "+     'aaaaaaaaaa',",
+            "+     'aaaaaaaaaa',",
+            "+     'aaaaaaaaaa',",
             "  ]",
         ]
 
@@ -555,31 +666,45 @@ def test_dict_wrap(self) -> None:
         diff = callequal(d1, d2, verbose=True)
         assert diff == [
             "{'common': 1,...1, 'env2': 2}} == {'common': 1,...: {'env1': 1}}",
+            "",
             "Omitting 1 identical items, use -vv to show",
             "Differing items:",
             "{'env': {'env1': 1, 'env2': 2}} != {'env': {'env1': 1}}",
+            "",
             "Full diff:",
-            "- {'common': 1, 'env': {'env1': 1}}",
-            "+ {'common': 1, 'env': {'env1': 1, 'env2': 2}}",
-            "?                                +++++++++++",
+            "  {",
+            "      'common': 1,",
+            "      'env': {",
+            "          'env1': 1,",
+            "+         'env2': 2,",
+            "      },",
+            "  }",
         ]
 
         long_a = "a" * 80
-        sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 2}}
+        sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 3}}
         d1 = {"env": {"sub": sub}}
         d2 = {"env": {"sub": sub}, "new": 1}
         diff = callequal(d1, d2, verbose=True)
         assert diff == [
             "{'env': {'sub... wrapped '}}}} == {'env': {'sub...}}}, 'new': 1}",
+            "",
             "Omitting 1 identical items, use -vv to show",
             "Right contains 1 more item:",
             "{'new': 1}",
+            "",
             "Full diff:",
             "  {",
-            "   'env': {'sub': {'long_a': '" + long_a + "',",
-            "                   'sub1': {'long_a': 'substring that gets wrapped substring '",
-            "                                      'that gets wrapped '}}},",
-            "-  'new': 1,",
+            "      'env': {",
+            "          'sub': {",
+            f"              'long_a': '{long_a}',",
+            "              'sub1': {",
+            "                  'long_a': 'substring that gets wrapped substring that gets wrapped '",
+            "                  'substring that gets wrapped ',",
+            "              },",
+            "          },",
+            "      },",
+            "-     'new': 1,",
             "  }",
         ]
 
@@ -591,7 +716,7 @@ def test_dict(self) -> None:
     def test_dict_omitting(self) -> None:
         lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1})
         assert lines is not None
-        assert lines[1].startswith("Omitting 1 identical item")
+        assert lines[2].startswith("Omitting 1 identical item")
         assert "Common items" not in lines
         for line in lines[1:]:
             assert "b" not in line
@@ -600,60 +725,109 @@ def test_dict_omitting_with_verbosity_1(self) -> None:
         """Ensure differing items are visible for verbosity=1 (#1512)."""
         lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}, verbose=1)
         assert lines is not None
-        assert lines[1].startswith("Omitting 1 identical item")
-        assert lines[2].startswith("Differing items")
-        assert lines[3] == "{'a': 0} != {'a': 1}"
+        assert lines[1] == ""
+        assert lines[2].startswith("Omitting 1 identical item")
+        assert lines[3].startswith("Differing items")
+        assert lines[4] == "{'a': 0} != {'a': 1}"
         assert "Common items" not in lines
 
     def test_dict_omitting_with_verbosity_2(self) -> None:
         lines = callequal({"a": 0, "b": 1}, {"a": 1, "b": 1}, verbose=2)
         assert lines is not None
-        assert lines[1].startswith("Common items:")
-        assert "Omitting" not in lines[1]
-        assert lines[2] == "{'b': 1}"
+        assert lines[2].startswith("Common items:")
+        assert "Omitting" not in lines[2]
+        assert lines[3] == "{'b': 1}"
 
     def test_dict_different_items(self) -> None:
         lines = callequal({"a": 0}, {"b": 1, "c": 2}, verbose=2)
         assert lines == [
             "{'a': 0} == {'b': 1, 'c': 2}",
+            "",
             "Left contains 1 more item:",
             "{'a': 0}",
             "Right contains 2 more items:",
             "{'b': 1, 'c': 2}",
+            "",
             "Full diff:",
-            "- {'b': 1, 'c': 2}",
-            "+ {'a': 0}",
+            "  {",
+            "-     'b': 1,",
+            "?      ^   ^",
+            "+     'a': 0,",
+            "?      ^   ^",
+            "-     'c': 2,",
+            "  }",
         ]
         lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2)
         assert lines == [
             "{'b': 1, 'c': 2} == {'a': 0}",
+            "",
             "Left contains 2 more items:",
             "{'b': 1, 'c': 2}",
             "Right contains 1 more item:",
             "{'a': 0}",
+            "",
             "Full diff:",
-            "- {'a': 0}",
-            "+ {'b': 1, 'c': 2}",
+            "  {",
+            "-     'a': 0,",
+            "?      ^   ^",
+            "+     'b': 1,",
+            "?      ^   ^",
+            "+     'c': 2,",
+            "  }",
         ]
 
     def test_sequence_different_items(self) -> None:
         lines = callequal((1, 2), (3, 4, 5), verbose=2)
         assert lines == [
             "(1, 2) == (3, 4, 5)",
+            "",
             "At index 0 diff: 1 != 3",
             "Right contains one more item: 5",
+            "",
             "Full diff:",
-            "- (3, 4, 5)",
-            "+ (1, 2)",
+            "  (",
+            "-     3,",
+            "?     ^",
+            "+     1,",
+            "?     ^",
+            "-     4,",
+            "?     ^",
+            "+     2,",
+            "?     ^",
+            "-     5,",
+            "  )",
         ]
         lines = callequal((1, 2, 3), (4,), verbose=2)
         assert lines == [
             "(1, 2, 3) == (4,)",
+            "",
             "At index 0 diff: 1 != 4",
             "Left contains 2 more items, first extra item: 2",
+            "",
             "Full diff:",
-            "- (4,)",
-            "+ (1, 2, 3)",
+            "  (",
+            "-     4,",
+            "?     ^",
+            "+     1,",
+            "?     ^",
+            "+     2,",
+            "+     3,",
+            "  )",
+        ]
+        lines = callequal((1, 2, 3), (1, 20, 3), verbose=2)
+        assert lines == [
+            "(1, 2, 3) == (1, 20, 3)",
+            "",
+            "At index 1 diff: 2 != 20",
+            "",
+            "Full diff:",
+            "  (",
+            "      1,",
+            "-     20,",
+            "?      -",
+            "+     2,",
+            "      3,",
+            "  )",
         ]
 
     def test_set(self) -> None:
@@ -684,7 +858,7 @@ def __setitem__(self, item, value):
             def __delitem__(self, item):
                 pass
 
-            def insert(self, item, index):
+            def insert(self, index, value):
                 pass
 
         expl = callequal(TestSequence([0, 1]), list([0, 2]))
@@ -699,32 +873,6 @@ def test_list_tuples(self) -> None:
         assert expl is not None
         assert len(expl) > 1
 
-    def test_repr_verbose(self) -> None:
-        class Nums:
-            def __init__(self, nums):
-                self.nums = nums
-
-            def __repr__(self):
-                return str(self.nums)
-
-        list_x = list(range(5000))
-        list_y = list(range(5000))
-        list_y[len(list_y) // 2] = 3
-        nums_x = Nums(list_x)
-        nums_y = Nums(list_y)
-
-        assert callequal(nums_x, nums_y) is None
-
-        expl = callequal(nums_x, nums_y, verbose=1)
-        assert expl is not None
-        assert "+" + repr(nums_x) in expl
-        assert "-" + repr(nums_y) in expl
-
-        expl = callequal(nums_x, nums_y, verbose=2)
-        assert expl is not None
-        assert "+" + repr(nums_x) in expl
-        assert "-" + repr(nums_y) in expl
-
     def test_list_bad_repr(self) -> None:
         class A:
             def __repr__(self):
@@ -737,11 +885,9 @@ def __repr__(self):
         assert expl is not None
         assert expl[0].startswith("{} == <[ValueError")
         assert "raised in repr" in expl[0]
-        assert expl[1:] == [
+        assert expl[2:] == [
             "(pytest_assertion plugin: representation of details failed:"
-            " {}:{}: ValueError: 42.".format(
-                __file__, A.__repr__.__code__.co_firstlineno + 1
-            ),
+            f" {__file__}:{A.__repr__.__code__.co_firstlineno + 1}: ValueError: 42.",
             " Probably an object has a faulty __repr__.)",
         ]
 
@@ -763,6 +909,7 @@ def test_repr_no_exc(self) -> None:
     def test_unicode(self) -> None:
         assert callequal("£€", "£") == [
             "'£€' == '£'",
+            "",
             "- £",
             "+ £€",
         ]
@@ -778,7 +925,7 @@ def __repr__(self):
                 return "\xff"
 
         expl = callequal(A(), "1")
-        assert expl == ["ÿ == '1'", "- 1"]
+        assert expl == ["ÿ == '1'", "", "- 1"]
 
     def test_format_nonascii_explanation(self) -> None:
         assert util.format_explanation("λ")
@@ -794,9 +941,28 @@ def test_mojibake(self) -> None:
         msg = "\n".join(expl)
         assert msg
 
+    def test_nfc_nfd_same_string(self) -> None:
+        # issue 3426
+        left = "hyv\xe4"
+        right = "hyva\u0308"
+        expl = callequal(left, right)
+        assert expl == [
+            r"'hyv\xe4' == 'hyva\u0308'",
+            "",
+            f"- {right!s}",
+            f"+ {left!s}",
+        ]
+
+        expl = callequal(left, right, verbose=2)
+        assert expl == [
+            r"'hyv\xe4' == 'hyva\u0308'",
+            "",
+            f"- {right!s}",
+            f"+ {left!s}",
+        ]
+
 
 class TestAssert_reprcompare_dataclass:
-    @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
     def test_dataclasses(self, pytester: Pytester) -> None:
         p = pytester.copy_example("dataclasses/test_compare_dataclasses.py")
         result = pytester.runpytest(p)
@@ -808,14 +974,13 @@ def test_dataclasses(self, pytester: Pytester) -> None:
                 "E         ['field_b']",
                 "E         ",
                 "E         Drill down into differing attribute field_b:",
-                "E           field_b: 'b' != 'c'...",
-                "E         ",
-                "E         ...Full output truncated (3 lines hidden), use '-vv' to show",
+                "E           field_b: 'b' != 'c'",
+                "E           - c",
+                "E           + b",
             ],
             consecutive=True,
         )
 
-    @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
     def test_recursive_dataclasses(self, pytester: Pytester) -> None:
         p = pytester.copy_example("dataclasses/test_compare_recursive_dataclasses.py")
         result = pytester.runpytest(p)
@@ -829,12 +994,11 @@ def test_recursive_dataclasses(self, pytester: Pytester) -> None:
                 "E         Drill down into differing attribute g:",
                 "E           g: S(a=10, b='ten') != S(a=20, b='xxx')...",
                 "E         ",
-                "E         ...Full output truncated (52 lines hidden), use '-vv' to show",
+                "E         ...Full output truncated (51 lines hidden), use '-vv' to show",
             ],
             consecutive=True,
         )
 
-    @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
     def test_recursive_dataclasses_verbose(self, pytester: Pytester) -> None:
         p = pytester.copy_example("dataclasses/test_compare_recursive_dataclasses.py")
         result = pytester.runpytest(p, "-vv")
@@ -854,8 +1018,6 @@ def test_recursive_dataclasses_verbose(self, pytester: Pytester) -> None:
                 "E           ",
                 "E           Drill down into differing attribute a:",
                 "E             a: 10 != 20",
-                "E             +10",
-                "E             -20",
                 "E           ",
                 "E           Drill down into differing attribute b:",
                 "E             b: 'ten' != 'xxx'",
@@ -867,7 +1029,6 @@ def test_recursive_dataclasses_verbose(self, pytester: Pytester) -> None:
             consecutive=True,
         )
 
-    @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
     def test_dataclasses_verbose(self, pytester: Pytester) -> None:
         p = pytester.copy_example("dataclasses/test_compare_dataclasses_verbose.py")
         result = pytester.runpytest(p, "-vv")
@@ -881,7 +1042,6 @@ def test_dataclasses_verbose(self, pytester: Pytester) -> None:
             ]
         )
 
-    @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
     def test_dataclasses_with_attribute_comparison_off(
         self, pytester: Pytester
     ) -> None:
@@ -891,7 +1051,6 @@ def test_dataclasses_with_attribute_comparison_off(
         result = pytester.runpytest(p, "-vv")
         result.assert_outcomes(failed=0, passed=1)
 
-    @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
     def test_comparing_two_different_data_classes(self, pytester: Pytester) -> None:
         p = pytester.copy_example(
             "dataclasses/test_compare_two_different_dataclasses.py"
@@ -899,6 +1058,22 @@ def test_comparing_two_different_data_classes(self, pytester: Pytester) -> None:
         result = pytester.runpytest(p, "-vv")
         result.assert_outcomes(failed=0, passed=1)
 
+    def test_data_classes_with_custom_eq(self, pytester: Pytester) -> None:
+        p = pytester.copy_example(
+            "dataclasses/test_compare_dataclasses_with_custom_eq.py"
+        )
+        # issue 9362
+        result = pytester.runpytest(p, "-vv")
+        result.assert_outcomes(failed=1, passed=0)
+        result.stdout.no_re_match_line(".*Differing attributes.*")
+
+    def test_data_classes_with_initvar(self, pytester: Pytester) -> None:
+        p = pytester.copy_example("dataclasses/test_compare_initvar.py")
+        # issue 9820
+        result = pytester.runpytest(p, "-vv")
+        result.assert_outcomes(failed=1, passed=0)
+        result.stdout.no_re_match_line(".*AttributeError.*")
+
 
 class TestAssert_reprcompare_attrsclass:
     def test_attrs(self) -> None:
@@ -982,7 +1157,6 @@ class SimpleDataObject:
         right = SimpleDataObject(1, "b")
 
         lines = callequal(left, right, verbose=2)
-        print(lines)
         assert lines is not None
         assert lines[2].startswith("Matching attributes:")
         assert "Omitting" not in lines[1]
@@ -1007,10 +1181,42 @@ class SimpleDataObjectTwo:
         lines = callequal(left, right)
         assert lines is None
 
+    def test_attrs_with_auto_detect_and_custom_eq(self) -> None:
+        @attr.s(
+            auto_detect=True
+        )  # attr.s doesn't ignore a custom eq if auto_detect=True
+        class SimpleDataObject:
+            field_a = attr.ib()
+
+            def __eq__(self, other):  # pragma: no cover
+                return super().__eq__(other)
+
+        left = SimpleDataObject(1)
+        right = SimpleDataObject(2)
+        # issue 9362
+        lines = callequal(left, right, verbose=2)
+        assert lines is None
+
+    def test_attrs_with_custom_eq(self) -> None:
+        @attr.define(slots=False)
+        class SimpleDataObject:
+            field_a = attr.ib()
+
+            def __eq__(self, other):  # pragma: no cover
+                return super().__eq__(other)
+
+        left = SimpleDataObject(1)
+        right = SimpleDataObject(2)
+        # issue 9362
+        lines = callequal(left, right, verbose=2)
+        assert lines is None
+
 
 class TestAssert_reprcompare_namedtuple:
     def test_namedtuple(self) -> None:
-        NT = collections.namedtuple("NT", ["a", "b"])
+        class NT(NamedTuple):
+            a: Any
+            b: Any
 
         left = NT(1, "b")
         right = NT(1, "c")
@@ -1027,12 +1233,17 @@ def test_namedtuple(self) -> None:
             "  b: 'b' != 'c'",
             "  - c",
             "  + b",
-            "Use -v to get the full diff",
+            "Use -v to get more diff",
         ]
 
     def test_comparing_two_different_namedtuple(self) -> None:
-        NT1 = collections.namedtuple("NT1", ["a", "b"])
-        NT2 = collections.namedtuple("NT2", ["a", "b"])
+        class NT1(NamedTuple):
+            a: Any
+            b: Any
+
+        class NT2(NamedTuple):
+            a: Any
+            b: Any
 
         left = NT1(1, "b")
         right = NT2(2, "b")
@@ -1041,8 +1252,9 @@ def test_comparing_two_different_namedtuple(self) -> None:
         # Because the types are different, uses the generic sequence matcher.
         assert lines == [
             "NT1(a=1, b='b') == NT2(a=2, b='b')",
+            "",
             "At index 0 diff: 1 != 2",
-            "Use -v to get the full diff",
+            "Use -v to get more diff",
         ]
 
 
@@ -1139,7 +1351,7 @@ class TestTruncateExplanation:
     LINES_IN_TRUNCATION_MSG = 2
 
     def test_doesnt_truncate_when_input_is_empty_list(self) -> None:
-        expl: List[str] = []
+        expl: list[str] = []
         result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100)
         assert result == expl
 
@@ -1151,30 +1363,55 @@ def test_doesnt_truncate_at_when_input_is_5_lines_and_LT_max_chars(self) -> None
     def test_truncates_at_8_lines_when_given_list_of_empty_strings(self) -> None:
         expl = ["" for x in range(50)]
         result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100)
+        assert len(result) != len(expl)
         assert result != expl
         assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
         assert "Full output truncated" in result[-1]
-        assert "43 lines hidden" in result[-1]
+        assert "42 lines hidden" in result[-1]
         last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1]
         assert last_line_before_trunc_msg.endswith("...")
 
     def test_truncates_at_8_lines_when_first_8_lines_are_LT_max_chars(self) -> None:
-        expl = ["a" for x in range(100)]
+        total_lines = 100
+        expl = ["a" for x in range(total_lines)]
         result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
         assert result != expl
         assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
         assert "Full output truncated" in result[-1]
-        assert "93 lines hidden" in result[-1]
+        assert f"{total_lines - 8} lines hidden" in result[-1]
         last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1]
         assert last_line_before_trunc_msg.endswith("...")
 
+    def test_truncates_at_8_lines_when_there_is_one_line_to_remove(self) -> None:
+        """The number of line in the result is 9, the same number as if we truncated."""
+        expl = ["a" for x in range(9)]
+        result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
+        assert result == expl
+        assert "truncated" not in result[-1]
+
+    def test_truncates_edgecase_when_truncation_message_makes_the_result_longer_for_chars(
+        self,
+    ) -> None:
+        line = "a" * 10
+        expl = [line, line]
+        result = truncate._truncate_explanation(expl, max_lines=10, max_chars=10)
+        assert result == [line, line]
+
+    def test_truncates_edgecase_when_truncation_message_makes_the_result_longer_for_lines(
+        self,
+    ) -> None:
+        line = "a" * 10
+        expl = [line, line]
+        result = truncate._truncate_explanation(expl, max_lines=1, max_chars=100)
+        assert result == [line, line]
+
     def test_truncates_at_8_lines_when_first_8_lines_are_EQ_max_chars(self) -> None:
-        expl = ["a" * 80 for x in range(16)]
+        expl = [chr(97 + x) * 80 for x in range(16)]
         result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
         assert result != expl
-        assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
+        assert len(result) == 16 - 8 + self.LINES_IN_TRUNCATION_MSG
         assert "Full output truncated" in result[-1]
-        assert "9 lines hidden" in result[-1]
+        assert "8 lines hidden" in result[-1]
         last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1]
         assert last_line_before_trunc_msg.endswith("...")
 
@@ -1200,20 +1437,18 @@ def test_truncates_at_1_line_when_first_line_is_GT_max_chars(self) -> None:
 
     def test_full_output_truncated(self, monkeypatch, pytester: Pytester) -> None:
         """Test against full runpytest() output."""
-
         line_count = 7
         line_len = 100
         expected_truncated_lines = 2
         pytester.makepyfile(
-            r"""
+            rf"""
             def test_many_lines():
-                a = list([str(i)[0] * %d for i in range(%d)])
+                a = list([str(i)[0] * {line_len} for i in range({line_count})])
                 b = a[::2]
                 a = '\n'.join(map(str, a))
                 b = '\n'.join(map(str, b))
                 assert a == b
         """
-            % (line_len, line_count)
         )
         monkeypatch.delenv("CI", raising=False)
 
@@ -1223,8 +1458,7 @@ def test_many_lines():
             [
                 "*+ 1*",
                 "*+ 3*",
-                "*+ 5*",
-                "*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines,
+                f"*truncated ({expected_truncated_lines} lines hidden)*use*-vv*",
             ]
         )
 
@@ -1235,6 +1469,66 @@ def test_many_lines():
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(["* 6*"])
 
+    @pytest.mark.parametrize(
+        ["truncation_lines", "truncation_chars", "expected_lines_hidden"],
+        (
+            (3, None, 3),
+            (4, None, 0),
+            (0, None, 0),
+            (None, 8, 6),
+            (None, 9, 0),
+            (None, 0, 0),
+            (0, 0, 0),
+            (0, 1000, 0),
+            (1000, 0, 0),
+        ),
+    )
+    def test_truncation_with_ini(
+        self,
+        monkeypatch,
+        pytester: Pytester,
+        truncation_lines: int | None,
+        truncation_chars: int | None,
+        expected_lines_hidden: int,
+    ) -> None:
+        pytester.makepyfile(
+            """\
+            string_a = "123456789\\n23456789\\n3"
+            string_b = "123456789\\n23456789\\n4"
+
+            def test():
+                assert string_a == string_b
+            """
+        )
+
+        # This test produces 6 lines of diff output or 79 characters
+        # So the effect should be when threshold is < 4 lines (considering 2 additional lines for explanation)
+        # Or < 9 characters (considering 70 additional characters for explanation)
+
+        monkeypatch.delenv("CI", raising=False)
+
+        ini = "[pytest]\n"
+        if truncation_lines is not None:
+            ini += f"truncation_limit_lines = {truncation_lines}\n"
+        if truncation_chars is not None:
+            ini += f"truncation_limit_chars = {truncation_chars}\n"
+        pytester.makeini(ini)
+
+        result = pytester.runpytest()
+
+        if expected_lines_hidden != 0:
+            result.stdout.fnmatch_lines(
+                [f"*truncated ({expected_lines_hidden} lines hidden)*"]
+            )
+        else:
+            result.stdout.no_fnmatch_line("*truncated*")
+            result.stdout.fnmatch_lines(
+                [
+                    "*- 4*",
+                    "*+ 3*",
+                ]
+            )
+
 
 def test_python25_compile_issue257(pytester: Pytester) -> None:
     pytester.makepyfile(
@@ -1267,6 +1561,7 @@ def test_rewritten():
 def test_reprcompare_notin() -> None:
     assert callop("not in", "foo", "aaafoobbb") == [
         "'foo' not in 'aaafoobbb'",
+        "",
         "'foo' is contained here:",
         "  aaafoobbb",
         "?    +++",
@@ -1276,6 +1571,7 @@ def test_reprcompare_notin() -> None:
 def test_reprcompare_whitespaces() -> None:
     assert callequal("\r\n", "\n") == [
         r"'\r\n' == '\n'",
+        "",
         r"Strings contain only whitespace, escaping them using repr()",
         r"- '\n'",
         r"+ '\r\n'",
@@ -1283,72 +1579,104 @@ def test_reprcompare_whitespaces() -> None:
     ]
 
 
-def test_pytest_assertrepr_compare_integration(pytester: Pytester) -> None:
-    pytester.makepyfile(
+class TestSetAssertions:
+    @pytest.mark.parametrize("op", [">=", ">", "<=", "<", "=="])
+    def test_set_extra_item(self, op, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            f"""
+            def test_hello():
+                x = set("hello x")
+                y = set("hello y")
+                assert x {op} y
         """
-        def test_hello():
-            x = set(range(100))
-            y = x.copy()
-            y.remove(50)
-            assert x == y
-    """
-    )
-    result = pytester.runpytest()
-    result.stdout.fnmatch_lines(
-        [
-            "*def test_hello():*",
-            "*assert x == y*",
-            "*E*Extra items*left*",
-            "*E*50*",
-            "*= 1 failed in*",
-        ]
-    )
+        )
+
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(
+            [
+                "*def test_hello():*",
+                f"*assert x {op} y*",
+            ]
+        )
+        if op in [">=", ">", "=="]:
+            result.stdout.fnmatch_lines(
+                [
+                    "*E*Extra items in the right set:*",
+                    "*E*'y'",
+                ]
+            )
+        if op in ["<=", "<", "=="]:
+            result.stdout.fnmatch_lines(
+                [
+                    "*E*Extra items in the left set:*",
+                    "*E*'x'",
+                ]
+            )
+
+    @pytest.mark.parametrize("op", [">", "<", "!="])
+    def test_set_proper_superset_equal(self, pytester: Pytester, op) -> None:
+        pytester.makepyfile(
+            f"""
+            def test_hello():
+                x = set([1, 2, 3])
+                y = x.copy()
+                assert x {op} y
+        """
+        )
 
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(
+            [
+                "*def test_hello():*",
+                f"*assert x {op} y*",
+                "*E*Both sets are equal*",
+            ]
+        )
 
-def test_sequence_comparison_uses_repr(pytester: Pytester) -> None:
-    pytester.makepyfile(
+    def test_pytest_assertrepr_compare_integration(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            def test_hello():
+                x = set(range(100))
+                y = x.copy()
+                y.remove(50)
+                assert x == y
         """
-        def test_hello():
-            x = set("hello x")
-            y = set("hello y")
-            assert x == y
-    """
-    )
-    result = pytester.runpytest()
-    result.stdout.fnmatch_lines(
-        [
-            "*def test_hello():*",
-            "*assert x == y*",
-            "*E*Extra items*left*",
-            "*E*'x'*",
-            "*E*Extra items*right*",
-            "*E*'y'*",
-        ]
-    )
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(
+            [
+                "*def test_hello():*",
+                "*assert x == y*",
+                "*E*Extra items*left*",
+                "*E*50*",
+                "*= 1 failed in*",
+            ]
+        )
 
 
 def test_assertrepr_loaded_per_dir(pytester: Pytester) -> None:
     pytester.makepyfile(test_base=["def test_base(): assert 1 == 2"])
     a = pytester.mkdir("a")
-    a.joinpath("test_a.py").write_text("def test_a(): assert 1 == 2")
+    a.joinpath("test_a.py").write_text("def test_a(): assert 1 == 2", encoding="utf-8")
     a.joinpath("conftest.py").write_text(
-        'def pytest_assertrepr_compare(): return ["summary a"]'
+        'def pytest_assertrepr_compare(): return ["summary a"]', encoding="utf-8"
     )
     b = pytester.mkdir("b")
-    b.joinpath("test_b.py").write_text("def test_b(): assert 1 == 2")
+    b.joinpath("test_b.py").write_text("def test_b(): assert 1 == 2", encoding="utf-8")
     b.joinpath("conftest.py").write_text(
-        'def pytest_assertrepr_compare(): return ["summary b"]'
+        'def pytest_assertrepr_compare(): return ["summary b"]', encoding="utf-8"
     )
 
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
         [
-            "*def test_base():*",
-            "*E*assert 1 == 2*",
             "*def test_a():*",
             "*E*assert summary a*",
             "*def test_b():*",
             "*E*assert summary b*",
+            "*def test_base():*",
+            "*E*assert 1 == 2*",
         ]
     )
 
@@ -1513,9 +1841,9 @@ def test_something():
     )
     result = pytester.runpytest("--collect-only")
     result.stdout.fnmatch_lines(
-        """
-        <Module*>
-    """
+        [
+            "  <Module*>",
+        ]
     )
 
 
@@ -1616,7 +1944,7 @@ def test_raise_assertion_error():
     )
 
 
-def test_raise_assertion_error_raisin_repr(pytester: Pytester) -> None:
+def test_raise_assertion_error_raising_repr(pytester: Pytester) -> None:
     pytester.makepyfile(
         """
         class RaisingRepr(object):
@@ -1627,9 +1955,7 @@ def test_raising_repr():
     """
     )
     result = pytester.runpytest()
-    result.stdout.fnmatch_lines(
-        ["E       AssertionError: <unprintable AssertionError object>"]
-    )
+    result.stdout.fnmatch_lines(["E       AssertionError: <exception str() failed>"])
 
 
 def test_issue_1944(pytester: Pytester) -> None:
@@ -1677,3 +2003,182 @@ def test():
             "*= 1 failed in*",
         ]
     )
+
+
+def test_reprcompare_verbose_long() -> None:
+    a = {f"v{i}": i for i in range(11)}
+    b = a.copy()
+    b["v2"] += 10
+    lines = callop("==", a, b, verbose=2)
+    assert lines is not None
+    assert lines[0] == (
+        "{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4, 'v5': 5, "
+        "'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}"
+        " == "
+        "{'v0': 0, 'v1': 1, 'v2': 12, 'v3': 3, 'v4': 4, 'v5': 5, "
+        "'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}"
+    )
+
+
+@pytest.mark.parametrize("enable_colors", [True, False])
+@pytest.mark.parametrize(
+    ("test_code", "expected_lines"),
+    (
+        (
+            """
+            def test():
+                assert [0, 1] == [0, 2]
+            """,
+            [
+                "{bold}{red}E         At index 1 diff: {reset}{number}1{hl-reset}{endline} != {reset}{number}2*",
+                "{bold}{red}E         {light-red}-     2,{hl-reset}{endline}{reset}",
+                "{bold}{red}E         {light-green}+     1,{hl-reset}{endline}{reset}",
+            ],
+        ),
+        (
+            """
+            def test():
+                assert {f"number-is-{i}": i for i in range(1, 6)} == {
+                    f"number-is-{i}": i for i in range(5)
+                }
+            """,
+            [
+                "{bold}{red}E         Common items:{reset}",
+                "{bold}{red}E         {reset}{{{str}'{hl-reset}{str}number-is-1{hl-reset}{str}'{hl-reset}: {number}1*",
+                "{bold}{red}E         Left contains 1 more item:{reset}",
+                "{bold}{red}E         {reset}{{{str}'{hl-reset}{str}number-is-5{hl-reset}{str}'{hl-reset}: {number}5*",
+                "{bold}{red}E         Right contains 1 more item:{reset}",
+                "{bold}{red}E         {reset}{{{str}'{hl-reset}{str}number-is-0{hl-reset}{str}'{hl-reset}: {number}0*",
+                "{bold}{red}E         {reset}{light-gray} {hl-reset} {{{endline}{reset}",
+                "{bold}{red}E         {light-gray} {hl-reset}     'number-is-1': 1,{endline}{reset}",
+                "{bold}{red}E         {light-green}+     'number-is-5': 5,{hl-reset}{endline}{reset}",
+            ],
+        ),
+        (
+            """
+            def test():
+                assert "abcd" == "abce"
+            """,
+            [
+                "{bold}{red}E         {reset}{light-red}- abce{hl-reset}{endline}{reset}",
+                "{bold}{red}E         {light-green}+ abcd{hl-reset}{endline}{reset}",
+            ],
+        ),
+    ),
+)
+def test_comparisons_handle_colors(
+    pytester: Pytester, color_mapping, enable_colors, test_code, expected_lines
+) -> None:
+    p = pytester.makepyfile(test_code)
+    result = pytester.runpytest(
+        f"--color={'yes' if enable_colors else 'no'}", "-vv", str(p)
+    )
+    formatter = (
+        color_mapping.format_for_fnmatch
+        if enable_colors
+        else color_mapping.strip_colors
+    )
+
+    result.stdout.fnmatch_lines(formatter(expected_lines), consecutive=False)
+
+
+def test_fine_grained_assertion_verbosity(pytester: Pytester):
+    long_text = "Lorem ipsum dolor sit amet " * 10
+    p = pytester.makepyfile(
+        f"""
+        def test_ok():
+            pass
+
+
+        def test_words_fail():
+            fruits1 = ["banana", "apple", "grapes", "melon", "kiwi"]
+            fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
+            assert fruits1 == fruits2
+
+
+        def test_numbers_fail():
+            number_to_text1 = {{str(x): x for x in range(5)}}
+            number_to_text2 = {{str(x * 10): x * 10 for x in range(5)}}
+            assert number_to_text1 == number_to_text2
+
+
+        def test_long_text_fail():
+            long_text = "{long_text}"
+            assert "hello world" in long_text
+        """
+    )
+    pytester.makeini(
+        """
+        [pytest]
+        verbosity_assertions = 2
+        """
+    )
+    result = pytester.runpytest(p)
+
+    result.stdout.fnmatch_lines(
+        [
+            f"{p.name} .FFF                            [100%]",
+            "E         At index 2 diff: 'grapes' != 'orange'",
+            "E         Full diff:",
+            "E           [",
+            "E               'banana',",
+            "E               'apple',",
+            "E         -     'orange',",
+            "E         ?      ^  ^^",
+            "E         +     'grapes',",
+            "E         ?      ^  ^ +",
+            "E               'melon',",
+            "E               'kiwi',",
+            "E           ]",
+            "E         Full diff:",
+            "E           {",
+            "E               '0': 0,",
+            "E         -     '10': 10,",
+            "E         ?       -    -",
+            "E         +     '1': 1,",
+            "E         -     '20': 20,",
+            "E         ?       -    -",
+            "E         +     '2': 2,",
+            "E         -     '30': 30,",
+            "E         ?       -    -",
+            "E         +     '3': 3,",
+            "E         -     '40': 40,",
+            "E         ?       -    -",
+            "E         +     '4': 4,",
+            "E           }",
+            f"E       AssertionError: assert 'hello world' in '{long_text}'",
+        ]
+    )
+
+
+def test_full_output_vvv(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        r"""
+        def crash_helper(m):
+            assert 1 == 2
+        def test_vvv():
+            crash_helper(500 * "a")
+    """
+    )
+    result = pytester.runpytest("")
+    # without -vvv, the passed args are truncated
+    expected_non_vvv_arg_line = "m = 'aaaaaaaaaaaaaaa*..aaaaaaaaaaaa*"
+    result.stdout.fnmatch_lines(
+        [
+            expected_non_vvv_arg_line,
+            "test_full_output_vvv.py:2: AssertionError",
+        ],
+    )
+    # double check that the untruncated part is not in the output
+    expected_vvv_arg_line = "m = '{}'".format(500 * "a")
+    result.stdout.no_fnmatch_line(expected_vvv_arg_line)
+
+    # but with "-vvv" the args are not truncated
+    result = pytester.runpytest("-vvv")
+    result.stdout.fnmatch_lines(
+        [
+            expected_vvv_arg_line,
+            "test_full_output_vvv.py:2: AssertionError",
+        ]
+    )
+    result.stdout.no_fnmatch_line(expected_non_vvv_arg_line)
diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py
index 61f5760e725..02d1c3e52ff 100644
--- a/testing/test_assertrewrite.py
+++ b/testing/test_assertrewrite.py
@@ -1,29 +1,33 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import ast
+from collections.abc import Generator
+from collections.abc import Mapping
+import dis
 import errno
+from functools import partial
 import glob
 import importlib
+import inspect
 import marshal
 import os
+from pathlib import Path
 import py_compile
+import re
 import stat
 import sys
 import textwrap
-import zipfile
-from functools import partial
-from pathlib import Path
 from typing import cast
-from typing import Dict
-from typing import List
-from typing import Mapping
-from typing import Optional
-from typing import Set
+from unittest import mock
+import zipfile
 
 import _pytest._code
-import pytest
 from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
 from _pytest.assertion import util
 from _pytest.assertion.rewrite import _get_assertion_exprs
 from _pytest.assertion.rewrite import _get_maxsize_for_saferepr
+from _pytest.assertion.rewrite import _saferepr
 from _pytest.assertion.rewrite import AssertionRewritingHook
 from _pytest.assertion.rewrite import get_cache_dir
 from _pytest.assertion.rewrite import PYC_TAIL
@@ -33,6 +37,7 @@
 from _pytest.config import ExitCode
 from _pytest.pathlib import make_numbered_dir
 from _pytest.pytester import Pytester
+import pytest
 
 
 def rewrite(src: str) -> ast.Module:
@@ -42,13 +47,13 @@ def rewrite(src: str) -> ast.Module:
 
 
 def getmsg(
-    f, extra_ns: Optional[Mapping[str, object]] = None, *, must_pass: bool = False
-) -> Optional[str]:
+    f, extra_ns: Mapping[str, object] | None = None, *, must_pass: bool = False
+) -> str | None:
     """Rewrite the assertions in f, run it, and get the failure message."""
     src = "\n".join(_pytest._code.Code.from_function(f).source().lines)
     mod = rewrite(src)
     code = compile(mod, "<test>", "exec")
-    ns: Dict[str, object] = {}
+    ns: dict[str, object] = {}
     if extra_ns is not None:
         ns.update(extra_ns)
     exec(code, ns)
@@ -127,11 +132,212 @@ def test_location_is_set(self) -> None:
             if isinstance(node, ast.Import):
                 continue
             for n in [node, *ast.iter_child_nodes(node)]:
-                assert n.lineno == 3
-                assert n.col_offset == 0
-                if sys.version_info >= (3, 8):
-                    assert n.end_lineno == 6
-                    assert n.end_col_offset == 3
+                assert isinstance(n, (ast.stmt, ast.expr))
+                for location in [
+                    (n.lineno, n.col_offset),
+                    (n.end_lineno, n.end_col_offset),
+                ]:
+                    assert (3, 0) <= location <= (6, 3)
+
+    def test_positions_are_preserved(self) -> None:
+        """Ensure AST positions are preserved during rewriting (#12818)."""
+
+        def preserved(code: str) -> None:
+            s = textwrap.dedent(code)
+            locations = []
+
+            def loc(msg: str | None = None) -> None:
+                frame = inspect.currentframe()
+                assert frame
+                frame = frame.f_back
+                assert frame
+                frame = frame.f_back
+                assert frame
+
+                offset = frame.f_lasti
+
+                instructions = {i.offset: i for i in dis.get_instructions(frame.f_code)}
+
+                # skip CACHE instructions
+                while offset not in instructions and offset >= 0:
+                    offset -= 1
+
+                instruction = instructions[offset]
+                if sys.version_info >= (3, 11):
+                    position = instruction.positions
+                else:
+                    position = instruction.starts_line
+
+                locations.append((msg, instruction.opname, position))
+
+            globals = {"loc": loc}
+
+            m = rewrite(s)
+            mod = compile(m, "<string>", "exec")
+            exec(mod, globals, globals)
+            transformed_locations = locations
+            locations = []
+
+            mod = compile(s, "<string>", "exec")
+            exec(mod, globals, globals)
+            original_locations = locations
+
+            assert len(original_locations) > 0
+            assert original_locations == transformed_locations
+
+        preserved("""
+            def f():
+                loc()
+                return 8
+
+            assert f() in [8]
+            assert (f()
+                    in
+                    [8])
+        """)
+
+        preserved("""
+            class T:
+                def __init__(self):
+                    loc("init")
+                def __getitem__(self,index):
+                    loc("getitem")
+                    return index
+
+            assert T()[5] == 5
+            assert (T
+                    ()
+                    [5]
+                    ==
+                    5)
+        """)
+
+        for name, op in [
+            ("pos", "+"),
+            ("neg", "-"),
+            ("invert", "~"),
+        ]:
+            preserved(f"""
+                class T:
+                    def __{name}__(self):
+                        loc("{name}")
+                        return "{name}"
+
+                assert {op}T() == "{name}"
+                assert ({op}
+                        T
+                        ()
+                        ==
+                        "{name}")
+            """)
+
+        for name, op in [
+            ("add", "+"),
+            ("sub", "-"),
+            ("mul", "*"),
+            ("truediv", "/"),
+            ("floordiv", "//"),
+            ("mod", "%"),
+            ("pow", "**"),
+            ("lshift", "<<"),
+            ("rshift", ">>"),
+            ("or", "|"),
+            ("xor", "^"),
+            ("and", "&"),
+            ("matmul", "@"),
+        ]:
+            preserved(f"""
+                class T:
+                    def __{name}__(self,other):
+                        loc("{name}")
+                        return other
+
+                    def __r{name}__(self,other):
+                        loc("r{name}")
+                        return other
+
+                assert T() {op} 2 == 2
+                assert 2 {op} T() == 2
+
+                assert (T
+                        ()
+                        {op}
+                        2
+                        ==
+                        2)
+
+                assert (2
+                        {op}
+                        T
+                        ()
+                        ==
+                        2)
+        """)
+
+        for name, op in [
+            ("eq", "=="),
+            ("ne", "!="),
+            ("lt", "<"),
+            ("le", "<="),
+            ("gt", ">"),
+            ("ge", ">="),
+        ]:
+            preserved(f"""
+                class T:
+                    def __{name}__(self,other):
+                        loc()
+                        return True
+
+                assert  T() {op} 5
+                assert  (T
+                        ()
+                        {op}
+                        5)
+        """)
+
+        for name, op in [
+            ("eq", "=="),
+            ("ne", "!="),
+            ("lt", ">"),
+            ("le", ">="),
+            ("gt", "<"),
+            ("ge", "<="),
+            ("contains", "in"),
+        ]:
+            preserved(f"""
+                class T:
+                    def __{name}__(self,other):
+                        loc()
+                        return True
+
+                assert  5 {op} T()
+                assert  (5
+                         {op}
+                         T
+                         ())
+        """)
+
+        preserved("""
+            def func(value):
+                loc("func")
+                return value
+
+            class T:
+                def __iter__(self):
+                    loc("iter")
+                    return iter([5])
+
+            assert  func(*T()) == 5
+        """)
+
+        preserved("""
+            class T:
+                def __getattr__(self,name):
+                    loc()
+                    return name
+
+            assert  T().attr == "attr"
+        """)
 
     def test_dont_rewrite(self) -> None:
         s = """'PYTEST_DONT_REWRITE'\nassert 14"""
@@ -158,7 +364,8 @@ def test_rewrites_plugin_as_a_package(self, pytester: Pytester) -> None:
             "def special_asserter():\n"
             "    def special_assert(x, y):\n"
             "        assert x == y\n"
-            "    return special_assert\n"
+            "    return special_assert\n",
+            encoding="utf-8",
         )
         pytester.makeconftest('pytest_plugins = ["plugin"]')
         pytester.makepyfile("def test(special_asserter): special_asserter(1, 2)\n")
@@ -171,7 +378,9 @@ def test_honors_pep_235(self, pytester: Pytester, monkeypatch) -> None:
         pytester.makepyfile(test_y="x = 1")
         xdir = pytester.mkdir("x")
         pytester.mkpydir(str(xdir.joinpath("test_Y")))
-        xdir.joinpath("test_Y").joinpath("__init__.py").write_text("x = 2")
+        xdir.joinpath("test_Y").joinpath("__init__.py").write_text(
+            "x = 2", encoding="utf-8"
+        )
         pytester.makepyfile(
             "import test_y\n"
             "import test_Y\n"
@@ -195,23 +404,15 @@ def f2() -> None:
         assert getmsg(f2) == "assert False"
 
         def f3() -> None:
-            assert a_global  # type: ignore[name-defined] # noqa
+            assert a_global  # type: ignore[name-defined] # noqa: F821
 
         assert getmsg(f3, {"a_global": False}) == "assert False"
 
         def f4() -> None:
             assert sys == 42  # type: ignore[comparison-overlap]
 
-        verbose = request.config.getoption("verbose")
         msg = getmsg(f4, {"sys": sys})
-        if verbose > 0:
-            assert msg == (
-                "assert <module 'sys' (built-in)> == 42\n"
-                "  +<module 'sys' (built-in)>\n"
-                "  -42"
-            )
-        else:
-            assert msg == "assert sys == 42"
+        assert msg == "assert sys == 42"
 
         def f5() -> None:
             assert cls == 42  # type: ignore[name-defined]  # noqa: F821
@@ -222,20 +423,7 @@ class X:
         msg = getmsg(f5, {"cls": X})
         assert msg is not None
         lines = msg.splitlines()
-        if verbose > 1:
-            assert lines == [
-                f"assert {X!r} == 42",
-                f"  +{X!r}",
-                "  -42",
-            ]
-        elif verbose > 0:
-            assert lines == [
-                "assert <class 'test_...e.<locals>.X'> == 42",
-                f"  +{X!r}",
-                "  -42",
-            ]
-        else:
-            assert lines == ["assert cls == 42"]
+        assert lines == ["assert cls == 42"]
 
     def test_assertrepr_compare_same_width(self, request) -> None:
         """Should use same width/truncation with same initial width."""
@@ -277,14 +465,11 @@ def f() -> None:
         msg = getmsg(f, {"cls": Y})
         assert msg is not None
         lines = msg.splitlines()
-        if request.config.getoption("verbose") > 0:
-            assert lines == ["assert 3 == 2", "  +3", "  -2"]
-        else:
-            assert lines == [
-                "assert 3 == 2",
-                " +  where 3 = Y.foo",
-                " +    where Y = cls()",
-            ]
+        assert lines == [
+            "assert 3 == 2",
+            " +  where 3 = Y.foo",
+            " +    where Y = cls()",
+        ]
 
     def test_assert_already_has_message(self) -> None:
         def f():
@@ -327,9 +512,7 @@ def test_foo():
         )
         result = pytester.runpytest()
         assert result.ret == 1
-        result.stdout.fnmatch_lines(
-            ["*AssertionError*%s*" % repr((1, 2)), "*assert 1 == 2*"]
-        )
+        result.stdout.fnmatch_lines([f"*AssertionError*{(1, 2)!r}*", "*assert 1 == 2*"])
 
     def test_assertion_message_expr(self, pytester: Pytester) -> None:
         pytester.makepyfile(
@@ -361,6 +544,34 @@ def test_assertion_messages_bytes(self, pytester: Pytester) -> None:
         assert result.ret == 1
         result.stdout.fnmatch_lines(["*AssertionError: b'ohai!'", "*assert False"])
 
+    def test_assertion_message_verbosity(self, pytester: Pytester) -> None:
+        """
+        Obey verbosity levels when printing the "message" part of assertions, when they are
+        non-strings (#6682).
+        """
+        pytester.makepyfile(
+            """
+            class LongRepr:
+
+                def __repr__(self):
+                    return "A" * 500
+
+            def test_assertion_verbosity():
+                assert False, LongRepr()
+            """
+        )
+        # Normal verbosity: assertion message gets abbreviated.
+        result = pytester.runpytest()
+        assert result.ret == 1
+        result.stdout.re_match_lines(
+            [r".*AssertionError: A+\.\.\.A+$", ".*assert False"]
+        )
+
+        # High-verbosity: do not abbreviate the assertion message.
+        result = pytester.runpytest("-vv")
+        assert result.ret == 1
+        result.stdout.re_match_lines([r".*AssertionError: A+$", ".*assert False"])
+
     def test_boolop(self) -> None:
         def f1() -> None:
             f = g = False
@@ -448,7 +659,7 @@ def f1() -> None:
 
         def f2() -> None:
             x = 1
-            assert x == 1 or x == 2
+            assert x == 1 or x == 2  # noqa: PLR1714
 
         getmsg(f2, must_pass=True)
 
@@ -661,10 +872,7 @@ def f():
             assert len(values) == 11
 
         msg = getmsg(f)
-        if request.config.getoption("verbose") > 0:
-            assert msg == "assert 10 == 11\n  +10\n  -11"
-        else:
-            assert msg == "assert 10 == 11\n +  where 10 = len([0, 1, 2, 3, 4, 5, ...])"
+        assert msg == "assert 10 == 11\n +  where 10 = len([0, 1, 2, 3, 4, 5, ...])"
 
     def test_custom_reprcompare(self, monkeypatch) -> None:
         def my_reprcompare1(op, left, right) -> str:
@@ -708,6 +916,25 @@ def myany(x) -> bool:
         assert msg is not None
         assert "<MY42 object> < 0" in msg
 
+    def test_assert_handling_raise_in__iter__(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """\
+            class A:
+                def __iter__(self):
+                    raise ValueError()
+
+                def __eq__(self, o: object) -> bool:
+                    return self is o
+
+                def __repr__(self):
+                    return "<A object>"
+
+            assert A() == A()
+            """
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(["*E*assert <A object> == <A object>"])
+
     def test_formatchar(self) -> None:
         def f() -> None:
             assert "%test" == "test"  # type: ignore[comparison-overlap]
@@ -730,10 +957,7 @@ def __repr__(self):
         msg = getmsg(f)
         assert msg is not None
         lines = util._format_lines([msg])
-        if request.config.getoption("verbose") > 0:
-            assert lines == ["assert 0 == 1\n  +0\n  -1"]
-        else:
-            assert lines == ["assert 0 == 1\n +  where 1 = \\n{ \\n~ \\n}.a"]
+        assert lines == ["assert 0 == 1\n +  where 1 = \\n{ \\n~ \\n}.a"]
 
     def test_custom_repr_non_ascii(self) -> None:
         def f() -> None:
@@ -751,10 +975,27 @@ def __repr__(self):
         assert "UnicodeDecodeError" not in msg
         assert "UnicodeEncodeError" not in msg
 
+    def test_assert_fixture(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """\
+        import pytest
+        @pytest.fixture
+        def fixt():
+            return 42
+
+        def test_something():  # missing "fixt" argument
+            assert fixt == 42
+            """
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(
+            ["*assert <pytest_fixture(<function fixt at *>)> == 42*"]
+        )
+
 
 class TestRewriteOnImport:
     def test_pycache_is_a_file(self, pytester: Pytester) -> None:
-        pytester.path.joinpath("__pycache__").write_text("Hello")
+        pytester.path.joinpath("__pycache__").write_text("Hello", encoding="utf-8")
         pytester.makepyfile(
             """
             def test_rewritten():
@@ -787,18 +1028,13 @@ def test_zipfile(self, pytester: Pytester) -> None:
             f.close()
         z.chmod(256)
         pytester.makepyfile(
-            """
+            f"""
             import sys
-            sys.path.append(%r)
+            sys.path.append({z_fn!r})
             import test_gum.test_lizard"""
-            % (z_fn,)
         )
         assert pytester.runpytest().ret == ExitCode.NO_TESTS_COLLECTED
 
-    @pytest.mark.skipif(
-        sys.version_info < (3, 9),
-        reason="importlib.resources.files was introduced in 3.9",
-    )
     def test_load_resource_via_files_with_rewrite(self, pytester: Pytester) -> None:
         example = pytester.path.joinpath("demo") / "example"
         init = pytester.path.joinpath("demo") / "__init__.py"
@@ -902,7 +1138,11 @@ def test_foo():
         )
 
     @pytest.mark.skipif('"__pypy__" in sys.modules')
-    def test_pyc_vs_pyo(self, pytester: Pytester, monkeypatch) -> None:
+    def test_pyc_vs_pyo(
+        self,
+        pytester: Pytester,
+        monkeypatch: pytest.MonkeyPatch,
+    ) -> None:
         pytester.makepyfile(
             """
             import pytest
@@ -911,14 +1151,14 @@ def test_optimized():
                 assert test_optimized.__doc__ is None"""
         )
         p = make_numbered_dir(root=Path(pytester.path), prefix="runpytest-")
-        tmp = "--basetemp=%s" % p
-        monkeypatch.setenv("PYTHONOPTIMIZE", "2")
-        monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
-        monkeypatch.delenv("PYTHONPYCACHEPREFIX", raising=False)
-        assert pytester.runpytest_subprocess(tmp).ret == 0
-        tagged = "test_pyc_vs_pyo." + PYTEST_TAG
-        assert tagged + ".pyo" in os.listdir("__pycache__")
-        monkeypatch.undo()
+        tmp = f"--basetemp={p}"
+        with monkeypatch.context() as mp:
+            mp.setenv("PYTHONOPTIMIZE", "2")
+            mp.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
+            mp.delenv("PYTHONPYCACHEPREFIX", raising=False)
+            assert pytester.runpytest_subprocess(tmp).ret == 0
+            tagged = "test_pyc_vs_pyo." + PYTEST_TAG
+            assert tagged + ".pyo" in os.listdir("__pycache__")
         monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
         monkeypatch.delenv("PYTHONPYCACHEPREFIX", raising=False)
         assert pytester.runpytest_subprocess(tmp).ret == 1
@@ -931,7 +1171,8 @@ def test_package(self, pytester: Pytester) -> None:
         pkg.joinpath("test_blah.py").write_text(
             """
 def test_rewritten():
-    assert "@py_builtins" in globals()"""
+    assert "@py_builtins" in globals()""",
+            encoding="utf-8",
         )
         assert pytester.runpytest().ret == 0
 
@@ -1037,9 +1278,9 @@ def test_meta_path():
         )
         assert pytester.runpytest().ret == 0
 
-    def test_write_pyc(self, pytester: Pytester, tmp_path, monkeypatch) -> None:
-        from _pytest.assertion.rewrite import _write_pyc
+    def test_write_pyc(self, pytester: Pytester, tmp_path) -> None:
         from _pytest.assertion import AssertionState
+        from _pytest.assertion.rewrite import _write_pyc
 
         config = pytester.parseconfig()
         state = AssertionState(config, "rewrite")
@@ -1049,27 +1290,8 @@ def test_write_pyc(self, pytester: Pytester, tmp_path, monkeypatch) -> None:
         co = compile("1", "f.py", "single")
         assert _write_pyc(state, co, os.stat(source_path), pycpath)
 
-        if sys.platform == "win32":
-            from contextlib import contextmanager
-
-            @contextmanager
-            def atomic_write_failed(fn, mode="r", overwrite=False):
-                e = OSError()
-                e.errno = 10
-                raise e
-                yield  # type:ignore[unreachable]
-
-            monkeypatch.setattr(
-                _pytest.assertion.rewrite, "atomic_write", atomic_write_failed
-            )
-        else:
-
-            def raise_oserror(*args):
-                raise OSError()
-
-            monkeypatch.setattr("os.rename", raise_oserror)
-
-        assert not _write_pyc(state, co, os.stat(source_path), pycpath)
+        with mock.patch.object(os, "replace", side_effect=OSError):
+            assert not _write_pyc(state, co, os.stat(source_path), pycpath)
 
     def test_resources_provider_for_loader(self, pytester: Pytester) -> None:
         """
@@ -1108,12 +1330,13 @@ def test_read_pyc(self, tmp_path: Path) -> None:
         an exception that is propagated to the caller.
         """
         import py_compile
+
         from _pytest.assertion.rewrite import _read_pyc
 
         source = tmp_path / "source.py"
         pyc = Path(str(source) + "c")
 
-        source.write_text("def test(): pass")
+        source.write_text("def test(): pass", encoding="utf-8")
         py_compile.compile(str(source), str(pyc))
 
         contents = pyc.read_bytes()
@@ -1123,9 +1346,28 @@ def test_read_pyc(self, tmp_path: Path) -> None:
 
         assert _read_pyc(source, pyc) is None  # no error
 
-    @pytest.mark.skipif(
-        sys.version_info < (3, 7), reason="Only the Python 3.7 format for simplicity"
-    )
+    def test_read_pyc_success(self, tmp_path: Path, pytester: Pytester) -> None:
+        """
+        Ensure that the _rewrite_test() -> _write_pyc() produces a pyc file
+        that can be properly read with _read_pyc()
+        """
+        from _pytest.assertion import AssertionState
+        from _pytest.assertion.rewrite import _read_pyc
+        from _pytest.assertion.rewrite import _rewrite_test
+        from _pytest.assertion.rewrite import _write_pyc
+
+        config = pytester.parseconfig()
+        state = AssertionState(config, "rewrite")
+
+        fn = tmp_path / "source.py"
+        pyc = Path(str(fn) + "c")
+
+        fn.write_text("def test(): assert True", encoding="utf-8")
+
+        source_stat, co = _rewrite_test(fn, config)
+        _write_pyc(state, co, source_stat, pyc)
+        assert _read_pyc(fn, pyc, state.trace) is not None
+
     def test_read_pyc_more_invalid(self, tmp_path: Path) -> None:
         from _pytest.assertion.rewrite import _read_pyc
 
@@ -1185,7 +1427,7 @@ def reloaded():
                 return False
 
             def rewrite_self():
-                with open(__file__, 'w') as self:
+                with open(__file__, 'w', encoding='utf-8') as self:
                     self.write('def reloaded(): return True')
             """,
             test_fun="""
@@ -1215,9 +1457,10 @@ def test_foo(self):
                         data = pkgutil.get_data('foo.test_foo', 'data.txt')
                         assert data == b'Hey'
                 """
-            )
+            ),
+            encoding="utf-8",
         )
-        path.joinpath("data.txt").write_text("Hey")
+        path.joinpath("data.txt").write_text("Hey", encoding="utf-8")
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(["*1 passed*"])
 
@@ -1293,8 +1536,284 @@ def test_simple_failure():
         result.stdout.fnmatch_lines(["*E*assert (1 + 1) == 3"])
 
 
+class TestIssue10743:
+    def test_assertion_walrus_operator(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            def my_func(before, after):
+                return before == after
+
+            def change_value(value):
+                return value.lower()
+
+            def test_walrus_conversion():
+                a = "Hello"
+                assert not my_func(a, a := change_value(a))
+                assert a == "hello"
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_walrus_operator_dont_rewrite(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            'PYTEST_DONT_REWRITE'
+            def my_func(before, after):
+                return before == after
+
+            def change_value(value):
+                return value.lower()
+
+            def test_walrus_conversion_dont_rewrite():
+                a = "Hello"
+                assert not my_func(a, a := change_value(a))
+                assert a == "hello"
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_inline_walrus_operator(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            def my_func(before, after):
+                return before == after
+
+            def test_walrus_conversion_inline():
+                a = "Hello"
+                assert not my_func(a, a := a.lower())
+                assert a == "hello"
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_inline_walrus_operator_reverse(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            def my_func(before, after):
+                return before == after
+
+            def test_walrus_conversion_reverse():
+                a = "Hello"
+                assert my_func(a := a.lower(), a)
+                assert a == 'hello'
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_walrus_no_variable_name_conflict(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def test_walrus_conversion_no_conflict():
+                a = "Hello"
+                assert a == (b := a.lower())
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"])
+
+    def test_assertion_walrus_operator_true_assertion_and_changes_variable_value(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def test_walrus_conversion_succeed():
+                a = "Hello"
+                assert a != (a := a.lower())
+                assert a == 'hello'
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_walrus_operator_fail_assertion(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            def test_walrus_conversion_fails():
+                a = "Hello"
+                assert a == (a := a.lower())
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"])
+
+    def test_assertion_walrus_operator_boolean_composite(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def test_walrus_operator_change_boolean_value():
+                a = True
+                assert a and True and ((a := False) is False) and (a is False) and ((a := None) is None)
+                assert a is None
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_walrus_operator_compare_boolean_fails(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def test_walrus_operator_change_boolean_value():
+                a = True
+                assert not (a and ((a := False) is False))
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines(["*assert not (True and False is False)"])
+
+    def test_assertion_walrus_operator_boolean_none_fails(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def test_walrus_operator_change_boolean_value():
+                a = True
+                assert not (a and ((a := None) is None))
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines(["*assert not (True and None is None)"])
+
+    def test_assertion_walrus_operator_value_changes_cleared_after_each_test(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def test_walrus_operator_change_value():
+                a = True
+                assert (a := None) is None
+
+            def test_walrus_operator_not_override_value():
+                a = True
+                assert a is True
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+
+class TestIssue11028:
+    def test_assertion_walrus_operator_in_operand(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            def test_in_string():
+              assert (obj := "foo") in obj
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_walrus_operator_in_operand_json_dumps(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            import json
+
+            def test_json_encoder():
+                assert (obj := "foo") in json.dumps(obj)
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_walrus_operator_equals_operand_function(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def f(a):
+                return a
+
+            def test_call_other_function_arg():
+              assert (obj := "foo") == f(obj)
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_walrus_operator_equals_operand_function_keyword_arg(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def f(a='test'):
+                return a
+
+            def test_call_other_function_k_arg():
+              assert (obj := "foo") == f(a=obj)
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_walrus_operator_equals_operand_function_arg_as_function(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def f(a='test'):
+                return a
+
+            def test_function_of_function():
+              assert (obj := "foo") == f(f(obj))
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+    def test_assertion_walrus_operator_gt_operand_function(
+        self, pytester: Pytester
+    ) -> None:
+        pytester.makepyfile(
+            """
+            def add_one(a):
+                return a + 1
+
+            def test_gt():
+              assert (obj := 4) > add_one(obj)
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 1
+        result.stdout.fnmatch_lines(["*assert 4 > 5", "*where 5 = add_one(4)"])
+
+
+class TestIssue11239:
+    def test_assertion_walrus_different_test_cases(self, pytester: Pytester) -> None:
+        """Regression for (#11239)
+
+        Walrus operator rewriting would leak to separate test cases if they used the same variables.
+        """
+        pytester.makepyfile(
+            """
+            def test_1():
+                state = {"x": 2}.get("x")
+                assert state is not None
+
+            def test_2():
+                db = {"x": 2}
+                assert (state := db.get("x")) is not None
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+
 @pytest.mark.skipif(
-    sys.maxsize <= (2 ** 31 - 1), reason="Causes OverflowError on 32bit systems"
+    sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems"
 )
 @pytest.mark.parametrize("offset", [-1, +1])
 def test_source_mtime_long_long(pytester: Pytester, offset) -> None:
@@ -1313,7 +1832,7 @@ def test(): pass
     # use unsigned long timestamp which overflows signed long,
     # which was the cause of the bug
     # +1 offset also tests masking of 0xFFFFFFFF
-    timestamp = 2 ** 32 + offset
+    timestamp = 2**32 + offset
     os.utime(str(p), (timestamp, timestamp))
     result = pytester.runpytest()
     assert result.ret == 0
@@ -1357,14 +1876,14 @@ class TestEarlyRewriteBailout:
     @pytest.fixture
     def hook(
         self, pytestconfig, monkeypatch, pytester: Pytester
-    ) -> AssertionRewritingHook:
+    ) -> Generator[AssertionRewritingHook]:
         """Returns a patched AssertionRewritingHook instance so we can configure its initial paths and track
         if PathFinder.find_spec has been called.
         """
         import importlib.machinery
 
-        self.find_spec_calls: List[str] = []
-        self.initial_paths: Set[Path] = set()
+        self.find_spec_calls: list[str] = []
+        self.initial_paths: set[Path] = set()
 
         class StubSession:
             _initialpaths = self.initial_paths
@@ -1378,11 +1897,11 @@ def spy_find_spec(name, path):
 
         hook = AssertionRewritingHook(pytestconfig)
         # use default patterns, otherwise we inherit pytest's testing config
-        hook.fnpats[:] = ["test_*.py", "*_test.py"]
-        monkeypatch.setattr(hook, "_find_spec", spy_find_spec)
-        hook.set_session(StubSession())  # type: ignore[arg-type]
-        pytester.syspathinsert()
-        return hook
+        with mock.patch.object(hook, "fnpats", ["test_*.py", "*_test.py"]):
+            monkeypatch.setattr(hook, "_find_spec", spy_find_spec)
+            hook.set_session(StubSession())  # type: ignore[arg-type]
+            pytester.syspathinsert()
+            yield hook
 
     def test_basic(self, pytester: Pytester, hook: AssertionRewritingHook) -> None:
         """
@@ -1432,9 +1951,9 @@ def test_simple_failure():
             }
         )
         pytester.syspathinsert("tests")
-        hook.fnpats[:] = ["tests/**.py"]
-        assert hook.find_spec("file") is not None
-        assert self.find_spec_calls == ["file"]
+        with mock.patch.object(hook, "fnpats", ["tests/**.py"]):
+            assert hook.find_spec("file") is not None
+            assert self.find_spec_calls == ["file"]
 
     @pytest.mark.skipif(
         sys.platform.startswith("win32"), reason="cannot remove cwd on Windows"
@@ -1455,8 +1974,8 @@ def test_cwd_changed(self, pytester: Pytester, monkeypatch) -> None:
                     import os
                     import tempfile
 
-                    with tempfile.TemporaryDirectory() as d:
-                        os.chdir(d)
+                    with tempfile.TemporaryDirectory() as newpath:
+                        os.chdir(newpath)
                 """,
                 "test_test.py": """\
                     def test():
@@ -1580,10 +2099,10 @@ def test_simple():
         result.assert_outcomes(passed=1)
 
 
+# fmt: off
 @pytest.mark.parametrize(
     ("src", "expected"),
     (
-        # fmt: off
         pytest.param(b"", {}, id="trivial"),
         pytest.param(
             b"def x(): assert 1\n",
@@ -1660,9 +2179,9 @@ def test_simple():
             {1: "5"},
             id="no newline at end of file",
         ),
-        # fmt: on
     ),
 )
+# fmt: on
 def test_get_assertion_exprs(src, expected) -> None:
     assert _get_assertion_exprs(src) == expected
 
@@ -1698,6 +2217,11 @@ def fake_mkdir(p, exist_ok=False, *, exc):
     monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err))
     assert not try_makedirs(p)
 
+    err = OSError()
+    err.errno = errno.ENOSYS
+    monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err))
+    assert not try_makedirs(p)
+
     # unhandled OSError should raise
     err = OSError()
     err.errno = errno.ECHILD
@@ -1719,16 +2243,10 @@ class TestPyCacheDir:
     )
     def test_get_cache_dir(self, monkeypatch, prefix, source, expected) -> None:
         monkeypatch.delenv("PYTHONPYCACHEPREFIX", raising=False)
-
-        if prefix is not None and sys.version_info < (3, 8):
-            pytest.skip("pycache_prefix not available in py<38")
         monkeypatch.setattr(sys, "pycache_prefix", prefix, raising=False)
 
         assert get_cache_dir(Path(source)) == Path(expected)
 
-    @pytest.mark.skipif(
-        sys.version_info < (3, 8), reason="pycache_prefix not available in py<38"
-    )
     @pytest.mark.skipif(
         sys.version_info[:2] == (3, 9) and sys.platform.startswith("win"),
         reason="#9298",
@@ -1764,8 +2282,8 @@ def test_foo():
         assert test_foo_pyc.is_file()
 
         # normal file: not touched by pytest, normal cache tag
-        bar_init_pyc = get_cache_dir(bar_init) / "__init__.{cache_tag}.pyc".format(
-            cache_tag=sys.implementation.cache_tag
+        bar_init_pyc = (
+            get_cache_dir(bar_init) / f"__init__.{sys.implementation.cache_tag}.pyc"
         )
         assert bar_init_pyc.is_file()
 
@@ -1787,13 +2305,15 @@ class TestReprSizeVerbosity:
     )
     def test_get_maxsize_for_saferepr(self, verbose: int, expected_size) -> None:
         class FakeConfig:
-            def getoption(self, name: str) -> int:
-                assert name == "verbose"
+            def get_verbosity(self, verbosity_type: str | None = None) -> int:
                 return verbose
 
         config = FakeConfig()
         assert _get_maxsize_for_saferepr(cast(Config, config)) == expected_size
 
+    def test_get_maxsize_for_saferepr_no_config(self) -> None:
+        assert _get_maxsize_for_saferepr(None) == DEFAULT_REPR_MAX_SIZE
+
     def create_test_file(self, pytester: Pytester, size: int) -> None:
         pytester.makepyfile(
             f"""
@@ -1817,3 +2337,40 @@ def test_max_increased_verbosity(self, pytester: Pytester) -> None:
         self.create_test_file(pytester, DEFAULT_REPR_MAX_SIZE * 10)
         result = pytester.runpytest("-vv")
         result.stdout.no_fnmatch_line("*xxx...xxx*")
+
+
+class TestIssue11140:
+    def test_constant_not_picked_as_module_docstring(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """\
+            0
+
+            def test_foo():
+                pass
+            """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+
+class TestSafereprUnbounded:
+    class Help:
+        def bound_method(self):  # pragma: no cover
+            pass
+
+    def test_saferepr_bound_method(self):
+        """saferepr() of a bound method should show only the method name"""
+        assert _saferepr(self.Help().bound_method) == "bound_method"
+
+    def test_saferepr_unbounded(self):
+        """saferepr() of an unbound method should still show the full information"""
+        obj = self.Help()
+        # using id() to fetch memory address fails on different platforms
+        pattern = re.compile(
+            rf"<{Path(__file__).stem}.{self.__class__.__name__}.Help object at 0x[0-9a-fA-F]*>",
+        )
+        assert pattern.match(_saferepr(obj))
+        assert (
+            _saferepr(self.Help)
+            == f"<class '{Path(__file__).stem}.{self.__class__.__name__}.Help'>"
+        )
diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py
index cc6d547dfb1..ca417e86ee5 100644
--- a/testing/test_cacheprovider.py
+++ b/testing/test_cacheprovider.py
@@ -1,14 +1,21 @@
+from __future__ import annotations
+
+from collections.abc import Generator
+from collections.abc import Sequence
+from enum import auto
+from enum import Enum
 import os
-import shutil
 from pathlib import Path
-from typing import Generator
-from typing import List
+import shutil
+from typing import Any
 
-import pytest
+from _pytest.compat import assert_never
 from _pytest.config import ExitCode
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
 from _pytest.tmpdir import TempPathFactory
+import pytest
+
 
 pytest_plugins = ("pytester",)
 
@@ -24,6 +31,21 @@ def test_config_cache_mkdir(self, pytester: Pytester) -> None:
         p = config.cache.mkdir("name")
         assert p.is_dir()
 
+    def test_cache_dir_permissions(self, pytester: Pytester) -> None:
+        """The .pytest_cache directory should have world-readable permissions
+        (depending on umask).
+
+        Regression test for #12308.
+        """
+        pytester.makeini("[pytest]")
+        config = pytester.parseconfigure()
+        assert config.cache is not None
+        p = config.cache.mkdir("name")
+        assert p.is_dir()
+        # Instead of messing with umask, make sure .pytest_cache has the same
+        # permissions as the default that `mkdir` gives `p`.
+        assert (p.parent.stat().st_mode & 0o777) == (p.stat().st_mode & 0o777)
+
     def test_config_cache_dataerror(self, pytester: Pytester) -> None:
         pytester.makeini("[pytest]")
         config = pytester.parseconfigure()
@@ -36,16 +58,18 @@ def test_config_cache_dataerror(self, pytester: Pytester) -> None:
         assert val == -2
 
     @pytest.mark.filterwarnings("ignore:could not create cache path")
-    def test_cache_writefail_cachfile_silent(self, pytester: Pytester) -> None:
+    def test_cache_writefail_cachefile_silent(self, pytester: Pytester) -> None:
         pytester.makeini("[pytest]")
-        pytester.path.joinpath(".pytest_cache").write_text("gone wrong")
+        pytester.path.joinpath(".pytest_cache").write_text(
+            "gone wrong", encoding="utf-8"
+        )
         config = pytester.parseconfigure()
         cache = config.cache
         assert cache is not None
         cache.set("test/broken", [])
 
     @pytest.fixture
-    def unwritable_cache_dir(self, pytester: Pytester) -> Generator[Path, None, None]:
+    def unwritable_cache_dir(self, pytester: Pytester) -> Generator[Path]:
         cache_dir = pytester.path.joinpath(".pytest_cache")
         cache_dir.mkdir()
         mode = cache_dir.stat().st_mode
@@ -80,16 +104,16 @@ def test_cache_failure_warns(
         pytester.makepyfile("def test_error(): raise Exception")
         result = pytester.runpytest()
         assert result.ret == 1
-        # warnings from nodeids, lastfailed, and stepwise
+        # warnings from nodeids and lastfailed
         result.stdout.fnmatch_lines(
             [
                 # Validate location/stacklevel of warning from cacheprovider.
                 "*= warnings summary =*",
                 "*/cacheprovider.py:*",
                 "  */cacheprovider.py:*: PytestCacheWarning: could not create cache path "
-                f"{unwritable_cache_dir}/v/cache/nodeids",
+                f"{unwritable_cache_dir}/v/cache/nodeids: *",
                 '    config.cache.set("cache/nodeids", sorted(self.cached_nodeids))',
-                "*1 failed, 3 warnings in*",
+                "*1 failed, 2 warnings in*",
             ]
         )
 
@@ -131,12 +155,10 @@ def test_cachefuncarg(cache):
     def test_custom_rel_cache_dir(self, pytester: Pytester) -> None:
         rel_cache_dir = os.path.join("custom_cache_dir", "subdir")
         pytester.makeini(
-            """
+            f"""
             [pytest]
-            cache_dir = {cache_dir}
-        """.format(
-                cache_dir=rel_cache_dir
-            )
+            cache_dir = {rel_cache_dir}
+        """
         )
         pytester.makepyfile(test_errored="def test_error():\n    assert False")
         pytester.runpytest()
@@ -148,12 +170,10 @@ def test_custom_abs_cache_dir(
         tmp = tmp_path_factory.mktemp("tmp")
         abs_cache_dir = tmp / "custom_cache_dir"
         pytester.makeini(
-            """
+            f"""
             [pytest]
-            cache_dir = {cache_dir}
-        """.format(
-                cache_dir=abs_cache_dir
-            )
+            cache_dir = {abs_cache_dir}
+        """
         )
         pytester.makepyfile(test_errored="def test_error():\n    assert False")
         pytester.runpytest()
@@ -167,17 +187,17 @@ def test_custom_cache_dir_with_env_var(
             """
             [pytest]
             cache_dir = {cache_dir}
-        """.format(
-                cache_dir="$env_var"
-            )
+        """.format(cache_dir="$env_var")
         )
         pytester.makepyfile(test_errored="def test_error():\n    assert False")
         pytester.runpytest()
         assert pytester.path.joinpath("custom_cache_dir").is_dir()
 
 
-@pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "/tox_env_dir")))
-def test_cache_reportheader(env, pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
+@pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "mydir/tox-env")))
+def test_cache_reportheader(
+    env: Sequence[str], pytester: Pytester, monkeypatch: MonkeyPatch
+) -> None:
     pytester.makepyfile("""def test_foo(): pass""")
     if env:
         monkeypatch.setenv(*env)
@@ -186,7 +206,7 @@ def test_cache_reportheader(env, pytester: Pytester, monkeypatch: MonkeyPatch) -
         monkeypatch.delenv("TOX_ENV_DIR", raising=False)
         expected = ".pytest_cache"
     result = pytester.runpytest("-v")
-    result.stdout.fnmatch_lines(["cachedir: %s" % expected])
+    result.stdout.fnmatch_lines([f"cachedir: {expected}"])
 
 
 def test_cache_reportheader_external_abspath(
@@ -198,12 +218,10 @@ def test_cache_reportheader_external_abspath(
 
     pytester.makepyfile("def test_hello(): pass")
     pytester.makeini(
-        """
+        f"""
     [pytest]
-    cache_dir = {abscache}
-    """.format(
-            abscache=external_cache
-        )
+    cache_dir = {external_cache}
+    """
     )
     result = pytester.runpytest("-v")
     result.stdout.fnmatch_lines([f"cachedir: {external_cache}"])
@@ -420,7 +438,13 @@ def test_fail(val):
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(["*1 failed in*"])
 
-    def test_terminal_report_lastfailed(self, pytester: Pytester) -> None:
+    @pytest.mark.parametrize("parent", ("directory", "package"))
+    def test_terminal_report_lastfailed(self, pytester: Pytester, parent: str) -> None:
+        if parent == "package":
+            pytester.makepyfile(
+                __init__="",
+            )
+
         test_a = pytester.makepyfile(
             test_a="""
             def test_a1(): pass
@@ -494,7 +518,6 @@ def test_a2(): pass
     def test_lastfailed_collectfailure(
         self, pytester: Pytester, monkeypatch: MonkeyPatch
     ) -> None:
-
         pytester.makepyfile(
             test_maybe="""
             import os
@@ -506,7 +529,7 @@ def test_hello():
         """
         )
 
-        def rlf(fail_import, fail_run):
+        def rlf(fail_import: int, fail_run: int) -> Any:
             monkeypatch.setenv("FAILIMPORT", str(fail_import))
             monkeypatch.setenv("FAILTEST", str(fail_run))
 
@@ -554,7 +577,9 @@ def test_pass():
         """
         )
 
-        def rlf(fail_import, fail_run, args=()):
+        def rlf(
+            fail_import: int, fail_run: int, args: Sequence[str] = ()
+        ) -> tuple[Any, Any]:
             monkeypatch.setenv("FAILIMPORT", str(fail_import))
             monkeypatch.setenv("FAILTEST", str(fail_run))
 
@@ -638,13 +663,11 @@ def test(): assert 0
         assert result.ret == 1
 
         pytester.makepyfile(
-            """
+            f"""
             import pytest
             @pytest.{mark}
             def test(): assert 0
-        """.format(
-                mark=mark
-            )
+        """
         )
         result = pytester.runpytest()
         assert result.ret == 0
@@ -670,7 +693,7 @@ def test_lf_and_ff_prints_no_needless_message(
         else:
             assert "rerun previous" in result.stdout.str()
 
-    def get_cached_last_failed(self, pytester: Pytester) -> List[str]:
+    def get_cached_last_failed(self, pytester: Pytester) -> list[str]:
         config = pytester.parseconfigure()
         assert config.cache is not None
         return sorted(config.cache.get("cache/lastfailed", {}))
@@ -773,7 +796,7 @@ def pytest_sessionfinish():
         result = pytester.runpytest("--lf", "--lfnf", "none")
         result.stdout.fnmatch_lines(
             [
-                "collected 2 items / 2 deselected",
+                "collected 2 items / 2 deselected / 0 selected",
                 "run-last-failure: no previously failed tests, deselecting all items.",
                 "deselected=2",
                 "* 2 deselected in *",
@@ -849,6 +872,33 @@ def test_3(): pass
             ]
         )
 
+    def test_lastfailed_skip_collection_with_nesting(self, pytester: Pytester) -> None:
+        """Check that file skipping works even when the file with failures is
+        nested at a different level of the collection tree."""
+        pytester.makepyfile(
+            **{
+                "test_1.py": """
+                    def test_1(): pass
+                """,
+                "pkg/__init__.py": "",
+                "pkg/test_2.py": """
+                    def test_2(): assert False
+                """,
+            }
+        )
+        # first run
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(["collected 2 items", "*1 failed*1 passed*"])
+        # second run - test_1.py is skipped.
+        result = pytester.runpytest("--lf")
+        result.stdout.fnmatch_lines(
+            [
+                "collected 1 item",
+                "run-last-failure: rerun previous 1 failure (skipped 1 file)",
+                "*= 1 failed in *",
+            ]
+        )
+
     def test_lastfailed_with_known_failures_not_being_selected(
         self, pytester: Pytester
     ) -> None:
@@ -902,8 +952,10 @@ def test_lastfailed_with_known_failures_not_being_selected(
                 "collected 1 item",
                 "run-last-failure: rerun previous 1 failure (skipped 1 file)",
                 "",
-                "<Module pkg1/test_1.py>",
-                "  <Function test_renamed>",
+                "<Dir *>",
+                "  <Dir pkg1>",
+                "    <Module test_1.py>",
+                "      <Function test_renamed>",
             ]
         )
 
@@ -932,8 +984,10 @@ def test_fail(): assert 0
                 "*collected 1 item",
                 "run-last-failure: 1 known failures not in selected tests",
                 "",
-                "<Module pkg1/test_1.py>",
-                "  <Function test_pass>",
+                "<Dir *>",
+                "  <Dir pkg1>",
+                "    <Module test_1.py>",
+                "      <Function test_pass>",
             ],
             consecutive=True,
         )
@@ -947,8 +1001,10 @@ def test_fail(): assert 0
                 "collected 2 items / 1 deselected / 1 selected",
                 "run-last-failure: rerun previous 1 failure",
                 "",
-                "<Module pkg1/test_1.py>",
-                "  <Function test_fail>",
+                "<Dir *>",
+                "  <Dir pkg1>",
+                "    <Module test_1.py>",
+                "      <Function test_fail>",
                 "*= 1/2 tests collected (1 deselected) in *",
             ],
         )
@@ -977,10 +1033,12 @@ def test_other(): assert 0
                 "collected 3 items / 1 deselected / 2 selected",
                 "run-last-failure: rerun previous 2 failures",
                 "",
-                "<Module pkg1/test_1.py>",
-                "  <Class TestFoo>",
-                "    <Function test_fail>",
-                "  <Function test_other>",
+                "<Dir *>",
+                "  <Dir pkg1>",
+                "    <Module test_1.py>",
+                "      <Class TestFoo>",
+                "        <Function test_fail>",
+                "      <Function test_other>",
                 "",
                 "*= 2/3 tests collected (1 deselected) in *",
             ],
@@ -1014,8 +1072,10 @@ def test_pass(): pass
                 "collected 1 item",
                 "run-last-failure: 1 known failures not in selected tests",
                 "",
-                "<Module pkg1/test_1.py>",
-                "  <Function test_pass>",
+                "<Dir *>",
+                "  <Dir pkg1>",
+                "    <Module test_1.py>",
+                "      <Function test_pass>",
                 "",
                 "*= 1 test collected in*",
             ],
@@ -1053,6 +1113,28 @@ def test_packages(self, pytester: Pytester) -> None:
         result = pytester.runpytest("--lf")
         result.assert_outcomes(failed=3)
 
+    def test_non_python_file_skipped(
+        self,
+        pytester: Pytester,
+        dummy_yaml_custom_test: None,
+    ) -> None:
+        pytester.makepyfile(
+            **{
+                "test_bad.py": """def test_bad(): assert False""",
+            },
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"])
+
+        result = pytester.runpytest("--lf")
+        result.stdout.fnmatch_lines(
+            [
+                "collected 1 item",
+                "run-last-failure: rerun previous 1 failure (skipped 1 file)",
+                "* 1 failed in *",
+            ]
+        )
+
 
 class TestNewFirst:
     def test_newfirst_usecase(self, pytester: Pytester) -> None:
@@ -1080,7 +1162,9 @@ def test_1(): assert 1
             ["*test_2/test_2.py::test_1 PASSED*", "*test_1/test_1.py::test_1 PASSED*"]
         )
 
-        p1.write_text("def test_1(): assert 1\n" "def test_2(): assert 1\n")
+        p1.write_text(
+            "def test_1(): assert 1\ndef test_2(): assert 1\n", encoding="utf-8"
+        )
         os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9)))
 
         result = pytester.runpytest("--nf", "--collect-only", "-q")
@@ -1153,7 +1237,8 @@ def test_1(num): assert num
         p1.write_text(
             "import pytest\n"
             "@pytest.mark.parametrize('num', [1, 2, 3])\n"
-            "def test_1(num): assert num\n"
+            "def test_1(num): assert num\n",
+            encoding="utf-8",
         )
         os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9)))
 
@@ -1193,20 +1278,41 @@ def test_readme_failed(self, pytester: Pytester) -> None:
         assert self.check_readme(pytester) is True
 
 
-def test_gitignore(pytester: Pytester) -> None:
+class Action(Enum):
+    """Action to perform on the cache directory."""
+
+    MKDIR = auto()
+    SET = auto()
+
+
+@pytest.mark.parametrize("action", list(Action))
+def test_gitignore(
+    pytester: Pytester,
+    action: Action,
+) -> None:
     """Ensure we automatically create .gitignore file in the pytest_cache directory (#3286)."""
     from _pytest.cacheprovider import Cache
 
     config = pytester.parseconfig()
     cache = Cache.for_config(config, _ispytest=True)
-    cache.set("foo", "bar")
+    if action == Action.MKDIR:
+        cache.mkdir("foo")
+    elif action == Action.SET:
+        cache.set("foo", "bar")
+    else:
+        assert_never(action)
     msg = "# Created by pytest automatically.\n*\n"
     gitignore_path = cache._cachedir.joinpath(".gitignore")
     assert gitignore_path.read_text(encoding="UTF-8") == msg
 
     # Does not overwrite existing/custom one.
-    gitignore_path.write_text("custom")
-    cache.set("something", "else")
+    gitignore_path.write_text("custom", encoding="utf-8")
+    if action == Action.MKDIR:
+        cache.mkdir("something")
+    elif action == Action.SET:
+        cache.set("something", "else")
+    else:
+        assert_never(action)
     assert gitignore_path.read_text(encoding="UTF-8") == "custom"
 
 
@@ -1249,3 +1355,8 @@ def test_cachedir_tag(pytester: Pytester) -> None:
     cache.set("foo", "bar")
     cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG")
     assert cachedir_tag_path.read_bytes() == CACHEDIR_TAG_CONTENT
+
+
+def test_clioption_with_cacheshow_and_help(pytester: Pytester) -> None:
+    result = pytester.runpytest("--cache-show", "--help")
+    assert result.ret == 0
diff --git a/testing/test_capture.py b/testing/test_capture.py
index 1bc1f2f8db2..d9dacebd938 100644
--- a/testing/test_capture.py
+++ b/testing/test_capture.py
@@ -1,16 +1,19 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Generator
 import contextlib
 import io
+from io import UnsupportedOperation
 import os
+import re
 import subprocess
 import sys
 import textwrap
-from io import UnsupportedOperation
 from typing import BinaryIO
 from typing import cast
-from typing import Generator
 from typing import TextIO
 
-import pytest
 from _pytest import capture
 from _pytest.capture import _get_multicapture
 from _pytest.capture import CaptureFixture
@@ -20,6 +23,8 @@
 from _pytest.config import ExitCode
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
+import pytest
+
 
 # note: py.io capture tests where copied from
 # pylib 1.4.20.dev2 (rev 13d9af95547e)
@@ -101,16 +106,15 @@ def test_init_capturing(self):
 def test_capturing_unicode(pytester: Pytester, method: str) -> None:
     obj = "'b\u00f6y'"
     pytester.makepyfile(
-        """\
+        f"""\
         # taken from issue 227 from nosetests
         def test_unicode():
             import sys
             print(sys.stdout)
-            print(%s)
+            print({obj})
         """
-        % obj
     )
-    result = pytester.runpytest("--capture=%s" % method)
+    result = pytester.runpytest(f"--capture={method}")
     result.stdout.fnmatch_lines(["*1 passed*"])
 
 
@@ -122,7 +126,7 @@ def test_unicode():
             print('b\\u00f6y')
         """
     )
-    result = pytester.runpytest("--capture=%s" % method)
+    result = pytester.runpytest(f"--capture={method}")
     result.stdout.fnmatch_lines(["*1 passed*"])
 
 
@@ -442,6 +446,38 @@ def test_hello(capsys):
         )
         reprec.assertoutcome(passed=1)
 
+    def test_capteesys(self, pytester: Pytester) -> None:
+        p = pytester.makepyfile(
+            """\
+            import sys
+            def test_one(capteesys):
+                print("sTdoUt")
+                print("sTdeRr", file=sys.stderr)
+                out, err = capteesys.readouterr()
+                assert out == "sTdoUt\\n"
+                assert err == "sTdeRr\\n"
+            """
+        )
+        # -rN and --capture=tee-sys means we'll read them on stdout/stderr,
+        # as opposed to both being reported on stdout
+        result = pytester.runpytest(p, "--quiet", "--quiet", "-rN", "--capture=tee-sys")
+        assert result.ret == ExitCode.OK
+        result.stdout.fnmatch_lines(["sTdoUt"])  # tee'd out
+        result.stderr.fnmatch_lines(["sTdeRr"])  # tee'd out
+
+        result = pytester.runpytest(p, "--quiet", "--quiet", "-rA", "--capture=tee-sys")
+        assert result.ret == ExitCode.OK
+        result.stdout.fnmatch_lines(
+            ["sTdoUt", "sTdoUt", "sTdeRr"]
+        )  # tee'd out, the next two reported
+        result.stderr.fnmatch_lines(["sTdeRr"])  # tee'd out
+
+        # -rA and --capture=sys means we'll read them on stdout.
+        result = pytester.runpytest(p, "--quiet", "--quiet", "-rA", "--capture=sys")
+        assert result.ret == ExitCode.OK
+        result.stdout.fnmatch_lines(["sTdoUt", "sTdeRr"])  # no tee, just reported
+        assert not result.stderr.lines
+
     def test_capsyscapfd(self, pytester: Pytester) -> None:
         p = pytester.makepyfile(
             """\
@@ -502,13 +538,11 @@ def test_capture_is_represented_on_failure_issue128(
         self, pytester: Pytester, method
     ) -> None:
         p = pytester.makepyfile(
-            """\
-            def test_hello(cap{}):
+            f"""\
+            def test_hello(cap{method}):
                 print("xxx42xxx")
                 assert 0
-            """.format(
-                method
-            )
+            """
         )
         result = pytester.runpytest(p)
         result.stdout.fnmatch_lines(["xxx42xxx"])
@@ -623,7 +657,7 @@ def test_disabled_capture_fixture(
         self, pytester: Pytester, fixture: str, no_capture: bool
     ) -> None:
         pytester.makepyfile(
-            """\
+            f"""\
             def test_disabled({fixture}):
                 print('captured before')
                 with {fixture}.disabled():
@@ -633,9 +667,7 @@ def test_disabled({fixture}):
 
             def test_normal():
                 print('test_normal executed')
-        """.format(
-                fixture=fixture
-            )
+        """
         )
         args = ("-s",) if no_capture else ()
         result = pytester.runpytest_subprocess(*args)
@@ -680,7 +712,7 @@ def test_fixture_use_by_other_fixtures(self, pytester: Pytester, fixture) -> Non
         """Ensure that capsys and capfd can be used by other fixtures during
         setup and teardown."""
         pytester.makepyfile(
-            """\
+            f"""\
             import sys
             import pytest
 
@@ -702,9 +734,7 @@ def test_captured_print(captured_print):
                 out, err = captured_print
                 assert out == 'stdout contents begin\\n'
                 assert err == 'stderr contents begin\\n'
-        """.format(
-                fixture=fixture
-            )
+        """
         )
         result = pytester.runpytest_subprocess()
         result.stdout.fnmatch_lines(["*1 passed*"])
@@ -717,7 +747,7 @@ def test_fixture_use_by_other_fixtures_teardown(
     ) -> None:
         """Ensure we can access setup and teardown buffers from teardown when using capsys/capfd (##3033)"""
         pytester.makepyfile(
-            """\
+            f"""\
             import sys
             import pytest
             import os
@@ -734,9 +764,7 @@ def fix({cap}):
             def test_a(fix):
                 print("call out")
                 sys.stderr.write("call err\\n")
-        """.format(
-                cap=cap
-            )
+        """
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=1)
@@ -750,9 +778,10 @@ def test_setup_failure_does_not_kill_capturing(pytester: Pytester) -> None:
             def pytest_runtest_setup(item):
                 raise ValueError(42)
             """
-        )
+        ),
+        encoding="utf-8",
     )
-    sub1.joinpath("test_mod.py").write_text("def test_func1(): pass")
+    sub1.joinpath("test_mod.py").write_text("def test_func1(): pass", encoding="utf-8")
     result = pytester.runpytest(pytester.path, "--traceconfig")
     result.stdout.fnmatch_lines(["*ValueError(42)*", "*1 error*"])
 
@@ -890,14 +919,26 @@ def test_dontreadfrominput() -> None:
     from _pytest.capture import DontReadFromInput
 
     f = DontReadFromInput()
-    assert f.buffer is f
+    assert f.buffer is f  # type: ignore[comparison-overlap]
     assert not f.isatty()
     pytest.raises(OSError, f.read)
     pytest.raises(OSError, f.readlines)
     iter_f = iter(f)
     pytest.raises(OSError, next, iter_f)
     pytest.raises(UnsupportedOperation, f.fileno)
+    pytest.raises(UnsupportedOperation, f.flush)
+    assert not f.readable()
+    pytest.raises(UnsupportedOperation, f.seek, 0)
+    assert not f.seekable()
+    pytest.raises(UnsupportedOperation, f.tell)
+    pytest.raises(UnsupportedOperation, f.truncate, 0)
+    pytest.raises(UnsupportedOperation, f.write, b"")
+    pytest.raises(UnsupportedOperation, f.writelines, [])
+    assert not f.writable()
+    assert isinstance(f.encoding, str)
     f.close()  # just for completeness
+    with f:
+        pass
 
 
 def test_captureresult() -> None:
@@ -931,7 +972,7 @@ def test_captureresult() -> None:
 
 
 @pytest.fixture
-def tmpfile(pytester: Pytester) -> Generator[BinaryIO, None, None]:
+def tmpfile(pytester: Pytester) -> Generator[BinaryIO]:
     f = pytester.makepyfile("").open("wb+")
     yield f
     if not f.closed:
@@ -1035,15 +1076,12 @@ def test_simple_resume_suspend(self) -> None:
             pytest.raises(AssertionError, cap.suspend)
 
             assert repr(cap) == (
-                "<FDCapture 1 oldfd={} _state='done' tmpfile={!r}>".format(
-                    cap.targetfd_save, cap.tmpfile
-                )
+                f"<FDCapture 1 oldfd={cap.targetfd_save} _state='done' tmpfile={cap.tmpfile!r}>"
             )
             # Should not crash with missing "_old".
+            assert isinstance(cap.syscapture, capture.SysCapture)
             assert repr(cap.syscapture) == (
-                "<SysCapture stdout _old=<UNSET> _state='done' tmpfile={!r}>".format(
-                    cap.syscapture.tmpfile
-                )
+                f"<SysCapture stdout _old=<UNSET> _state='done' tmpfile={cap.syscapture.tmpfile!r}>"
             )
 
     def test_capfd_sys_stdout_mode(self, capfd) -> None:
@@ -1184,7 +1222,6 @@ class TestTeeStdCapture(TestStdCapture):
     def test_capturing_error_recursive(self) -> None:
         r"""For TeeStdCapture since we passthrough stderr/stdout, cap1
         should get all output, while cap2 should only get "cap2\n"."""
-
         with self.getcapture() as cap1:
             print("cap1")
             with self.getcapture() as cap2:
@@ -1340,6 +1377,7 @@ def test_capsys_results_accessible_by_attribute(capsys: CaptureFixture[str]) ->
 
 def test_fdcapture_tmpfile_remains_the_same() -> None:
     cap = StdCaptureFD(out=False, err=True)
+    assert isinstance(cap.err, capture.FDCapture)
     try:
         cap.start_capturing()
         capfile = cap.err.tmpfile
@@ -1378,28 +1416,27 @@ def test_capture_again():
 def test_capturing_and_logging_fundamentals(pytester: Pytester, method: str) -> None:
     # here we check a fundamental feature
     p = pytester.makepyfile(
-        """
+        f"""
         import sys, os, logging
         from _pytest import capture
         cap = capture.MultiCapture(
             in_=None,
             out=None,
-            err=capture.%s,
+            err=capture.{method},
         )
         cap.start_capturing()
 
         logging.warning("hello1")
         outerr = cap.readouterr()
-        print("suspend, captured %%s" %%(outerr,))
+        print("suspend, captured %s" %(outerr,))
         logging.warning("hello2")
 
         cap.pop_outerr_to_orig()
         logging.warning("hello3")
 
         outerr = cap.readouterr()
-        print("suspend2, captured %%s" %% (outerr,))
+        print("suspend2, captured %s" % (outerr,))
     """
-        % (method,)
     )
     result = pytester.runpython(p)
     result.stdout.fnmatch_lines(
@@ -1433,19 +1470,19 @@ def test_capattr():
     not sys.platform.startswith("win"),
     reason="only on windows",
 )
-def test_py36_windowsconsoleio_workaround_non_standard_streams() -> None:
+def test_windowsconsoleio_workaround_non_standard_streams() -> None:
     """
-    Ensure _py36_windowsconsoleio_workaround function works with objects that
+    Ensure _windowsconsoleio_workaround function works with objects that
     do not implement the full ``io``-based stream protocol, for example execnet channels (#2666).
     """
-    from _pytest.capture import _py36_windowsconsoleio_workaround
+    from _pytest.capture import _windowsconsoleio_workaround
 
     class DummyStream:
         def write(self, s):
             pass
 
     stream = cast(TextIO, DummyStream())
-    _py36_windowsconsoleio_workaround(stream)
+    _windowsconsoleio_workaround(stream)
 
 
 def test_dontreadfrominput_has_encoding(pytester: Pytester) -> None:
@@ -1509,9 +1546,9 @@ def test_global_capture_with_live_logging(pytester: Pytester) -> None:
         def pytest_runtest_logreport(report):
             if "test_global" in report.nodeid:
                 if report.when == "teardown":
-                    with open("caplog", "w") as f:
+                    with open("caplog", "w", encoding="utf-8") as f:
                         f.write(report.caplog)
-                    with open("capstdout", "w") as f:
+                    with open("capstdout", "w", encoding="utf-8") as f:
                         f.write(report.capstdout)
         """
     )
@@ -1541,14 +1578,14 @@ def test_global(fix1):
     result = pytester.runpytest_subprocess("--log-cli-level=INFO")
     assert result.ret == 0
 
-    with open("caplog") as f:
+    with open("caplog", encoding="utf-8") as f:
         caplog = f.read()
 
     assert "fix setup" in caplog
     assert "something in test" in caplog
     assert "fix teardown" in caplog
 
-    with open("capstdout") as f:
+    with open("capstdout", encoding="utf-8") as f:
         capstdout = f.read()
 
     assert "fix setup" in capstdout
@@ -1565,16 +1602,16 @@ def test_capture_with_live_logging(
     # capture should work with live cli logging
 
     pytester.makepyfile(
-        """
+        f"""
         import logging
         import sys
 
         logger = logging.getLogger(__name__)
 
-        def test_capture({0}):
+        def test_capture({capture_fixture}):
             print("hello")
             sys.stderr.write("world\\n")
-            captured = {0}.readouterr()
+            captured = {capture_fixture}.readouterr()
             assert captured.out == "hello\\n"
             assert captured.err == "world\\n"
 
@@ -1582,11 +1619,9 @@ def test_capture({0}):
             print("next")
             logging.info("something")
 
-            captured = {0}.readouterr()
+            captured = {capture_fixture}.readouterr()
             assert captured.out == "next\\n"
-        """.format(
-            capture_fixture
-        )
+        """
     )
 
     result = pytester.runpytest_subprocess("--log-cli-level=INFO")
@@ -1664,3 +1699,32 @@ def test_logging():
     )
     result.stdout.no_fnmatch_line("*Captured stderr call*")
     result.stdout.no_fnmatch_line("*during collection*")
+
+
+def test_libedit_workaround(pytester: Pytester) -> None:
+    pytester.makeconftest("""
+    import pytest
+
+
+    def pytest_terminal_summary(config):
+        capture = config.pluginmanager.getplugin("capturemanager")
+        capture.suspend_global_capture(in_=True)
+
+        print("Enter 'hi'")
+        value = input()
+        print(f"value: {value!r}")
+
+        capture.resume_global_capture()
+    """)
+    readline = pytest.importorskip("readline")
+    backend = getattr(readline, "backend", readline.__doc__)  # added in Python 3.13
+    print(f"Readline backend: {backend}")
+
+    child = pytester.spawn_pytest("")
+    child.expect(r"Enter 'hi'")
+    child.sendline("hi")
+    rest = child.read().decode("utf8")
+    print(rest)
+    match = re.search(r"^value: '(.*)'\r?$", rest, re.MULTILINE)
+    assert match is not None
+    assert match.group(1) == "hi"
diff --git a/testing/test_collect_imported_tests.py b/testing/test_collect_imported_tests.py
new file mode 100644
index 00000000000..28b92e17f6f
--- /dev/null
+++ b/testing/test_collect_imported_tests.py
@@ -0,0 +1,102 @@
+"""Tests for the `collect_imported_tests` configuration value."""
+
+from __future__ import annotations
+
+import textwrap
+
+from _pytest.pytester import Pytester
+import pytest
+
+
+def setup_files(pytester: Pytester) -> None:
+    src_dir = pytester.mkdir("src")
+    tests_dir = pytester.mkdir("tests")
+    src_file = src_dir / "foo.py"
+
+    src_file.write_text(
+        textwrap.dedent("""\
+            class Testament:
+                def test_collections(self):
+                    pass
+
+            def test_testament(): pass
+        """),
+        encoding="utf-8",
+    )
+
+    test_file = tests_dir / "foo_test.py"
+    test_file.write_text(
+        textwrap.dedent("""\
+            from foo import Testament, test_testament
+
+            class TestDomain:
+                def test(self):
+                    testament = Testament()
+                    assert testament
+        """),
+        encoding="utf-8",
+    )
+
+    pytester.syspathinsert(src_dir)
+
+
+def test_collect_imports_disabled(pytester: Pytester) -> None:
+    """
+    When collect_imported_tests is disabled, only objects in the
+    test modules are collected as tests, so the imported names (`Testament` and `test_testament`)
+    are not collected.
+    """
+    pytester.makeini(
+        """
+        [pytest]
+        collect_imported_tests = false
+        """
+    )
+
+    setup_files(pytester)
+    result = pytester.runpytest("-v", "tests")
+    result.stdout.fnmatch_lines(
+        [
+            "tests/foo_test.py::TestDomain::test PASSED*",
+        ]
+    )
+
+    # Ensure that the hooks were only called for the collected item.
+    reprec = result.reprec  # type:ignore[attr-defined]
+    reports = reprec.getreports("pytest_collectreport")
+    [modified] = reprec.getcalls("pytest_collection_modifyitems")
+    [item_collected] = reprec.getcalls("pytest_itemcollected")
+
+    assert [x.nodeid for x in reports] == [
+        "",
+        "tests/foo_test.py::TestDomain",
+        "tests/foo_test.py",
+        "tests",
+    ]
+    assert [x.nodeid for x in modified.items] == ["tests/foo_test.py::TestDomain::test"]
+    assert item_collected.item.nodeid == "tests/foo_test.py::TestDomain::test"
+
+
+@pytest.mark.parametrize("configure_ini", [False, True])
+def test_collect_imports_enabled(pytester: Pytester, configure_ini: bool) -> None:
+    """
+    When collect_imported_tests is enabled (the default), all names in the
+    test modules are collected as tests.
+    """
+    if configure_ini:
+        pytester.makeini(
+            """
+            [pytest]
+            collect_imported_tests = true
+            """
+        )
+
+    setup_files(pytester)
+    result = pytester.runpytest("-v", "tests")
+    result.stdout.fnmatch_lines(
+        [
+            "tests/foo_test.py::Testament::test_collections PASSED*",
+            "tests/foo_test.py::test_testament PASSED*",
+            "tests/foo_test.py::TestDomain::test PASSED*",
+        ]
+    )
diff --git a/testing/test_collection.py b/testing/test_collection.py
index 6fd9a708b4e..ccd57eeef43 100644
--- a/testing/test_collection.py
+++ b/testing/test_collection.py
@@ -1,12 +1,16 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import os
+from pathlib import Path
+from pathlib import PurePath
 import pprint
 import shutil
 import sys
+import tempfile
 import textwrap
-from pathlib import Path
-from typing import List
 
-import pytest
+from _pytest.assertion.util import running_on_ci
 from _pytest.config import ExitCode
 from _pytest.fixtures import FixtureRequest
 from _pytest.main import _in_venv
@@ -16,6 +20,7 @@
 from _pytest.pathlib import symlink_or_skip
 from _pytest.pytester import HookRecorder
 from _pytest.pytester import Pytester
+import pytest
 
 
 def ensure_file(file_path: Path) -> Path:
@@ -64,7 +69,7 @@ def test_fail(): assert 0
 
         assert pytester.collect_by_name(modcol, "doesnotexist") is None
 
-    def test_getparent(self, pytester: Pytester) -> None:
+    def test_getparent_and_accessors(self, pytester: Pytester) -> None:
         modcol = pytester.getmodulecol(
             """
             class TestClass:
@@ -77,14 +82,21 @@ def test_foo(self):
         fn = pytester.collect_by_name(cls, "test_foo")
         assert isinstance(fn, pytest.Function)
 
-        module_parent = fn.getparent(pytest.Module)
-        assert module_parent is modcol
+        assert fn.getparent(pytest.Module) is modcol
+        assert modcol.module is not None
+        assert modcol.cls is None
+        assert modcol.instance is None
 
-        function_parent = fn.getparent(pytest.Function)
-        assert function_parent is fn
+        assert fn.getparent(pytest.Class) is cls
+        assert cls.module is not None
+        assert cls.cls is not None
+        assert cls.instance is None
 
-        class_parent = fn.getparent(pytest.Class)
-        assert class_parent is cls
+        assert fn.getparent(pytest.Function) is fn
+        assert fn.module is not None
+        assert fn.cls is not None
+        assert fn.instance is not None
+        assert fn.function is not None
 
     def test_getcustomfile_roundtrip(self, pytester: Pytester) -> None:
         hello = pytester.makefile(".xxx", hello="world")
@@ -92,7 +104,8 @@ def test_getcustomfile_roundtrip(self, pytester: Pytester) -> None:
             conftest="""
             import pytest
             class CustomFile(pytest.File):
-                pass
+                def collect(self):
+                    return []
             def pytest_collect_file(file_path, parent):
                 if file_path.suffix == ".xxx":
                     return CustomFile.from_parent(path=file_path, parent=parent)
@@ -133,29 +146,26 @@ def test_ignored_certain_directories(self, pytester: Pytester) -> None:
         ensure_file(tmp_path / ".bzr" / "test_notfound.py")
         ensure_file(tmp_path / "normal" / "test_found.py")
         for x in tmp_path.rglob("test_*.py"):
-            x.write_text("def test_hello(): pass", "utf-8")
+            x.write_text("def test_hello(): pass", encoding="utf-8")
 
         result = pytester.runpytest("--collect-only")
         s = result.stdout.str()
         assert "test_notfound" not in s
         assert "test_found" in s
 
-    @pytest.mark.parametrize(
-        "fname",
-        (
-            "activate",
-            "activate.csh",
-            "activate.fish",
-            "Activate",
-            "Activate.bat",
-            "Activate.ps1",
-        ),
+    known_environment_types = pytest.mark.parametrize(
+        "env_path",
+        [
+            pytest.param(PurePath("pyvenv.cfg"), id="venv"),
+            pytest.param(PurePath("conda-meta", "history"), id="conda"),
+        ],
     )
-    def test_ignored_virtualenvs(self, pytester: Pytester, fname: str) -> None:
-        bindir = "Scripts" if sys.platform.startswith("win") else "bin"
-        ensure_file(pytester.path / "virtual" / bindir / fname)
+
+    @known_environment_types
+    def test_ignored_virtualenvs(self, pytester: Pytester, env_path: PurePath) -> None:
+        ensure_file(pytester.path / "virtual" / env_path)
         testfile = ensure_file(pytester.path / "virtual" / "test_invenv.py")
-        testfile.write_text("def test_hello(): pass")
+        testfile.write_text("def test_hello(): pass", encoding="utf-8")
 
         # by default, ignore tests inside a virtualenv
         result = pytester.runpytest()
@@ -167,52 +177,28 @@ def test_ignored_virtualenvs(self, pytester: Pytester, fname: str) -> None:
         result = pytester.runpytest("virtual")
         assert "test_invenv" in result.stdout.str()
 
-    @pytest.mark.parametrize(
-        "fname",
-        (
-            "activate",
-            "activate.csh",
-            "activate.fish",
-            "Activate",
-            "Activate.bat",
-            "Activate.ps1",
-        ),
-    )
+    @known_environment_types
     def test_ignored_virtualenvs_norecursedirs_precedence(
-        self, pytester: Pytester, fname: str
+        self, pytester: Pytester, env_path
     ) -> None:
-        bindir = "Scripts" if sys.platform.startswith("win") else "bin"
         # norecursedirs takes priority
-        ensure_file(pytester.path / ".virtual" / bindir / fname)
+        ensure_file(pytester.path / ".virtual" / env_path)
         testfile = ensure_file(pytester.path / ".virtual" / "test_invenv.py")
-        testfile.write_text("def test_hello(): pass")
+        testfile.write_text("def test_hello(): pass", encoding="utf-8")
         result = pytester.runpytest("--collect-in-virtualenv")
         result.stdout.no_fnmatch_line("*test_invenv*")
         # ...unless the virtualenv is explicitly given on the CLI
         result = pytester.runpytest("--collect-in-virtualenv", ".virtual")
         assert "test_invenv" in result.stdout.str()
 
-    @pytest.mark.parametrize(
-        "fname",
-        (
-            "activate",
-            "activate.csh",
-            "activate.fish",
-            "Activate",
-            "Activate.bat",
-            "Activate.ps1",
-        ),
-    )
-    def test__in_venv(self, pytester: Pytester, fname: str) -> None:
+    @known_environment_types
+    def test__in_venv(self, pytester: Pytester, env_path: PurePath) -> None:
         """Directly test the virtual env detection function"""
-        bindir = "Scripts" if sys.platform.startswith("win") else "bin"
-        # no bin/activate, not a virtualenv
+        # no env path, not a env
         base_path = pytester.mkdir("venv")
         assert _in_venv(base_path) is False
-        # with bin/activate, totally a virtualenv
-        bin_path = base_path.joinpath(bindir)
-        bin_path.mkdir()
-        bin_path.joinpath(fname).touch()
+        # with env path, totally a env
+        ensure_file(base_path.joinpath(env_path))
         assert _in_venv(base_path) is True
 
     def test_custom_norecursedirs(self, pytester: Pytester) -> None:
@@ -224,10 +210,14 @@ def test_custom_norecursedirs(self, pytester: Pytester) -> None:
         )
         tmp_path = pytester.path
         ensure_file(tmp_path / "mydir" / "test_hello.py").write_text(
-            "def test_1(): pass"
+            "def test_1(): pass", encoding="utf-8"
+        )
+        ensure_file(tmp_path / "xyz123" / "test_2.py").write_text(
+            "def test_2(): 0/0", encoding="utf-8"
+        )
+        ensure_file(tmp_path / "xy" / "test_ok.py").write_text(
+            "def test_3(): pass", encoding="utf-8"
         )
-        ensure_file(tmp_path / "xyz123" / "test_2.py").write_text("def test_2(): 0/0")
-        ensure_file(tmp_path / "xy" / "test_ok.py").write_text("def test_3(): pass")
         rec = pytester.inline_run()
         rec.assertoutcome(passed=1)
         rec = pytester.inline_run("xyz123/test_2.py")
@@ -237,31 +227,54 @@ def test_testpaths_ini(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> No
         pytester.makeini(
             """
             [pytest]
-            testpaths = gui uts
+            testpaths = */tests
         """
         )
         tmp_path = pytester.path
-        ensure_file(tmp_path / "env" / "test_1.py").write_text("def test_env(): pass")
-        ensure_file(tmp_path / "gui" / "test_2.py").write_text("def test_gui(): pass")
-        ensure_file(tmp_path / "uts" / "test_3.py").write_text("def test_uts(): pass")
+        ensure_file(tmp_path / "a" / "test_1.py").write_text(
+            "def test_a(): pass", encoding="utf-8"
+        )
+        ensure_file(tmp_path / "b" / "tests" / "test_2.py").write_text(
+            "def test_b(): pass", encoding="utf-8"
+        )
+        ensure_file(tmp_path / "c" / "tests" / "test_3.py").write_text(
+            "def test_c(): pass", encoding="utf-8"
+        )
 
         # executing from rootdir only tests from `testpaths` directories
         # are collected
         items, reprec = pytester.inline_genitems("-v")
-        assert [x.name for x in items] == ["test_gui", "test_uts"]
+        assert [x.name for x in items] == ["test_b", "test_c"]
 
         # check that explicitly passing directories in the command-line
         # collects the tests
-        for dirname in ("env", "gui", "uts"):
+        for dirname in ("a", "b", "c"):
             items, reprec = pytester.inline_genitems(tmp_path.joinpath(dirname))
-            assert [x.name for x in items] == ["test_%s" % dirname]
+            assert [x.name for x in items] == [f"test_{dirname}"]
 
         # changing cwd to each subdirectory and running pytest without
         # arguments collects the tests in that directory normally
-        for dirname in ("env", "gui", "uts"):
+        for dirname in ("a", "b", "c"):
             monkeypatch.chdir(pytester.path.joinpath(dirname))
             items, reprec = pytester.inline_genitems()
-            assert [x.name for x in items] == ["test_%s" % dirname]
+            assert [x.name for x in items] == [f"test_{dirname}"]
+
+    def test_missing_permissions_on_unselected_directory_doesnt_crash(
+        self, pytester: Pytester
+    ) -> None:
+        """Regression test for #12120."""
+        test = pytester.makepyfile(test="def test(): pass")
+        bad = pytester.mkdir("bad")
+        try:
+            bad.chmod(0)
+
+            result = pytester.runpytest(test)
+        finally:
+            bad.chmod(750)
+            bad.rmdir()
+
+        assert result.ret == ExitCode.OK
+        result.assert_outcomes(passed=1)
 
 
 class TestCollectPluginHookRelay:
@@ -317,17 +330,39 @@ def test_collect_report_postprocessing(self, pytester: Pytester) -> None:
         pytester.makeconftest(
             """
             import pytest
-            @pytest.hookimpl(hookwrapper=True)
+            @pytest.hookimpl(wrapper=True)
             def pytest_make_collect_report():
-                outcome = yield
-                rep = outcome.get_result()
+                rep = yield
                 rep.headerlines += ["header1"]
-                outcome.force_result(rep)
+                return rep
         """
         )
         result = pytester.runpytest(p)
         result.stdout.fnmatch_lines(["*ERROR collecting*", "*header1*"])
 
+    def test_collection_error_traceback_is_clean(self, pytester: Pytester) -> None:
+        """When a collection error occurs, the report traceback doesn't contain
+        internal pytest stack entries.
+
+        Issue #11710.
+        """
+        pytester.makepyfile(
+            """
+            raise Exception("LOUSY")
+            """
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(
+            [
+                "*ERROR collecting*",
+                "test_*.py:1: in <module>",
+                '    raise Exception("LOUSY")',
+                "E   Exception: LOUSY",
+                "*= short test summary info =*",
+            ],
+            consecutive=True,
+        )
+
 
 class TestCustomConftests:
     def test_ignore_collect_path(self, pytester: Pytester) -> None:
@@ -338,8 +373,8 @@ def pytest_ignore_collect(collection_path, config):
         """
         )
         sub = pytester.mkdir("xy123")
-        ensure_file(sub / "test_hello.py").write_text("syntax error")
-        sub.joinpath("conftest.py").write_text("syntax error")
+        ensure_file(sub / "test_hello.py").write_text("syntax error", encoding="utf-8")
+        sub.joinpath("conftest.py").write_text("syntax error", encoding="utf-8")
         pytester.makepyfile("def test_hello(): pass")
         pytester.makepyfile(test_one="syntax error")
         result = pytester.runpytest("--fulltrace")
@@ -473,9 +508,9 @@ def test_collect_topdir(self, pytester: Pytester) -> None:
         # assert root2 == rcol, rootid
         colitems = rcol.perform_collect([rcol.nodeid], genitems=False)
         assert len(colitems) == 1
-        assert colitems[0].path == p
+        assert colitems[0].path == topdir
 
-    def get_reported_items(self, hookrec: HookRecorder) -> List[Item]:
+    def get_reported_items(self, hookrec: HookRecorder) -> list[Item]:
         """Return pytest.Item instances reported by the pytest_collectreport hook"""
         calls = hookrec.getcalls("pytest_collectreport")
         return [
@@ -494,7 +529,7 @@ def test_collect_protocol_single_function(self, pytester: Pytester) -> None:
         newid = item.nodeid
         assert newid == id
         pprint.pprint(hookrec.calls)
-        topdir = pytester.path  # noqa
+        topdir = pytester.path  # noqa: F841
         hookrec.assert_contains(
             [
                 ("pytest_collectstart", "collector.path == topdir"),
@@ -529,7 +564,7 @@ def test_method(self):
     def test_collect_custom_nodes_multi_id(self, pytester: Pytester) -> None:
         p = pytester.makepyfile("def test_func(): pass")
         pytester.makeconftest(
-            """
+            f"""
             import pytest
             class SpecialItem(pytest.Item):
                 def runtest(self):
@@ -538,10 +573,9 @@ class SpecialFile(pytest.File):
                 def collect(self):
                     return [SpecialItem.from_parent(name="check", parent=self)]
             def pytest_collect_file(file_path, parent):
-                if file_path.name == %r:
+                if file_path.name == {p.name!r}:
                     return SpecialFile.from_parent(path=file_path, parent=parent)
         """
-            % p.name
         )
         id = p.name
 
@@ -630,6 +664,23 @@ def test_method(self):
         # ensure we are reporting the collection of the single test item (#2464)
         assert [x.name for x in self.get_reported_items(hookrec)] == ["test_method"]
 
+    def test_collect_parametrized_order(self, pytester: Pytester) -> None:
+        p = pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize('i', [0, 1, 2])
+            def test_param(i): ...
+            """
+        )
+        items, hookrec = pytester.inline_genitems(f"{p}::test_param")
+        assert len(items) == 3
+        assert [item.nodeid for item in items] == [
+            "test_collect_parametrized_order.py::test_param[0]",
+            "test_collect_parametrized_order.py::test_param[1]",
+            "test_collect_parametrized_order.py::test_param[2]",
+        ]
+
 
 class Test_getinitialnodes:
     def test_global_file(self, pytester: Pytester) -> None:
@@ -640,11 +691,12 @@ def test_global_file(self, pytester: Pytester) -> None:
         assert isinstance(col, pytest.Module)
         assert col.name == "x.py"
         assert col.parent is not None
-        assert col.parent.parent is None
+        assert col.parent.parent is not None
+        assert col.parent.parent.parent is None
         for parent in col.listchain():
             assert parent.config is config
 
-    def test_pkgfile(self, pytester: Pytester) -> None:
+    def test_pkgfile(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
         """Verify nesting when a module is within a package.
         The parent chain should match: Module<x.py> -> Package<subdir> -> Session.
             Session's parent should always be None.
@@ -653,7 +705,8 @@ def test_pkgfile(self, pytester: Pytester) -> None:
         subdir = tmp_path.joinpath("subdir")
         x = ensure_file(subdir / "x.py")
         ensure_file(subdir / "__init__.py")
-        with subdir.cwd():
+        with monkeypatch.context() as mp:
+            mp.chdir(subdir)
             config = pytester.parseconfigure(x)
         col = pytester.getnode(config, x)
         assert col is not None
@@ -723,6 +776,20 @@ def testmethod_two(self, arg0):
         assert s.endswith("test_example_items1.testone")
         print(s)
 
+    def test_classmethod_is_discovered(self, pytester: Pytester) -> None:
+        """Test that classmethods are discovered"""
+        p = pytester.makepyfile(
+            """
+            class TestCase:
+                @classmethod
+                def test_classmethod(cls) -> None:
+                    pass
+            """
+        )
+        items, reprec = pytester.inline_genitems(p)
+        ids = [x.getmodpath() for x in items]  # type: ignore[attr-defined]
+        assert ids == ["TestCase.test_classmethod"]
+
     def test_class_and_functions_discovery_using_glob(self, pytester: Pytester) -> None:
         """Test that Python_classes and Python_functions config options work
         as prefixes and glob-like patterns (#600)."""
@@ -786,7 +853,7 @@ def runtest(self):
     result = pytester.runpytest()
     assert result.ret == 0
     result.stdout.fnmatch_lines(["*2 passed*"])
-    res = pytester.runpytest("%s::item2" % p.name)
+    res = pytester.runpytest(f"{p.name}::item2")
     res.stdout.fnmatch_lines(["*1 passed*"])
 
 
@@ -874,6 +941,76 @@ def test_method(self): pass
         assert item.keywords["kw"] == "method"
         assert len(item.keywords) == len(set(item.keywords))
 
+    def test_unpacked_marks_added_to_keywords(self, pytester: Pytester) -> None:
+        item = pytester.getitem(
+            """
+            import pytest
+            pytestmark = pytest.mark.foo
+            class TestClass:
+                pytestmark = pytest.mark.bar
+                def test_method(self): pass
+                test_method.pytestmark = pytest.mark.baz
+        """,
+            "test_method",
+        )
+        assert isinstance(item, pytest.Function)
+        cls = item.getparent(pytest.Class)
+        assert cls is not None
+        mod = item.getparent(pytest.Module)
+        assert mod is not None
+
+        assert item.keywords["foo"] == pytest.mark.foo.mark
+        assert item.keywords["bar"] == pytest.mark.bar.mark
+        assert item.keywords["baz"] == pytest.mark.baz.mark
+
+        assert cls.keywords["foo"] == pytest.mark.foo.mark
+        assert cls.keywords["bar"] == pytest.mark.bar.mark
+        assert "baz" not in cls.keywords
+
+        assert mod.keywords["foo"] == pytest.mark.foo.mark
+        assert "bar" not in mod.keywords
+        assert "baz" not in mod.keywords
+
+
+class TestCollectDirectoryHook:
+    def test_custom_directory_example(self, pytester: Pytester) -> None:
+        """Verify the example from the customdirectory.rst doc."""
+        pytester.copy_example("customdirectory")
+
+        reprec = pytester.inline_run()
+
+        reprec.assertoutcome(passed=2, failed=0)
+        calls = reprec.getcalls("pytest_collect_directory")
+        assert len(calls) == 2
+        assert calls[0].path == pytester.path
+        assert isinstance(calls[0].parent, pytest.Session)
+        assert calls[1].path == pytester.path / "tests"
+        assert isinstance(calls[1].parent, pytest.Dir)
+
+    def test_directory_ignored_if_none(self, pytester: Pytester) -> None:
+        """If the (entire) hook returns None, it's OK, the directory is ignored."""
+        pytester.makeconftest(
+            """
+            import pytest
+
+            @pytest.hookimpl(wrapper=True)
+            def pytest_collect_directory():
+                yield
+                return None
+            """,
+        )
+        pytester.makepyfile(
+            **{
+                "tests/test_it.py": """
+                    import pytest
+
+                    def test_it(): pass
+                """,
+            },
+        )
+        reprec = pytester.inline_run()
+        reprec.assertoutcome(passed=0, failed=0)
+
 
 COLLECTION_ERROR_PY_FILES = dict(
     test_01_failure="""
@@ -1004,13 +1141,18 @@ def test_fixture_scope_sibling_conftests(pytester: Pytester) -> None:
             def fix():
                 return 1
             """
-        )
+        ),
+        encoding="utf-8",
+    )
+    foo_path.joinpath("test_foo.py").write_text(
+        "def test_foo(fix): assert fix == 1", encoding="utf-8"
     )
-    foo_path.joinpath("test_foo.py").write_text("def test_foo(fix): assert fix == 1")
 
     # Tests in `food/` should not see the conftest fixture from `foo/`
     food_path = pytester.mkpydir("food")
-    food_path.joinpath("test_food.py").write_text("def test_food(fix): assert fix == 1")
+    food_path.joinpath("test_food.py").write_text(
+        "def test_food(fix): assert fix == 1", encoding="utf-8"
+    )
 
     res = pytester.runpytest()
     assert res.ret == 1
@@ -1031,22 +1173,24 @@ def test_collect_init_tests(pytester: Pytester) -> None:
     result.stdout.fnmatch_lines(
         [
             "collected 2 items",
-            "<Package tests>",
-            "  <Module __init__.py>",
-            "    <Function test_init>",
-            "  <Module test_foo.py>",
-            "    <Function test_foo>",
+            "<Dir *>",
+            "  <Package tests>",
+            "    <Module __init__.py>",
+            "      <Function test_init>",
+            "    <Module test_foo.py>",
+            "      <Function test_foo>",
         ]
     )
     result = pytester.runpytest("./tests", "--collect-only")
     result.stdout.fnmatch_lines(
         [
             "collected 2 items",
-            "<Package tests>",
-            "  <Module __init__.py>",
-            "    <Function test_init>",
-            "  <Module test_foo.py>",
-            "    <Function test_foo>",
+            "<Dir *>",
+            "  <Package tests>",
+            "    <Module __init__.py>",
+            "      <Function test_init>",
+            "    <Module test_foo.py>",
+            "      <Function test_foo>",
         ]
     )
     # Ignores duplicates with "." and pkginit (#4310).
@@ -1054,11 +1198,12 @@ def test_collect_init_tests(pytester: Pytester) -> None:
     result.stdout.fnmatch_lines(
         [
             "collected 2 items",
-            "<Package tests>",
-            "  <Module __init__.py>",
-            "    <Function test_init>",
-            "  <Module test_foo.py>",
-            "    <Function test_foo>",
+            "<Dir *>",
+            "  <Package tests>",
+            "    <Module __init__.py>",
+            "      <Function test_init>",
+            "    <Module test_foo.py>",
+            "      <Function test_foo>",
         ]
     )
     # Same as before, but different order.
@@ -1066,21 +1211,32 @@ def test_collect_init_tests(pytester: Pytester) -> None:
     result.stdout.fnmatch_lines(
         [
             "collected 2 items",
-            "<Package tests>",
-            "  <Module __init__.py>",
-            "    <Function test_init>",
-            "  <Module test_foo.py>",
-            "    <Function test_foo>",
+            "<Dir *>",
+            "  <Package tests>",
+            "    <Module __init__.py>",
+            "      <Function test_init>",
+            "    <Module test_foo.py>",
+            "      <Function test_foo>",
         ]
     )
     result = pytester.runpytest("./tests/test_foo.py", "--collect-only")
     result.stdout.fnmatch_lines(
-        ["<Package tests>", "  <Module test_foo.py>", "    <Function test_foo>"]
+        [
+            "<Dir *>",
+            "  <Package tests>",
+            "    <Module test_foo.py>",
+            "      <Function test_foo>",
+        ]
     )
     result.stdout.no_fnmatch_line("*test_init*")
     result = pytester.runpytest("./tests/__init__.py", "--collect-only")
     result.stdout.fnmatch_lines(
-        ["<Package tests>", "  <Module __init__.py>", "    <Function test_init>"]
+        [
+            "<Dir *>",
+            "  <Package tests>",
+            "    <Module __init__.py>",
+            "      <Function test_init>",
+        ]
     )
     result.stdout.no_fnmatch_line("*test_foo*")
 
@@ -1128,7 +1284,7 @@ def test_1():
     """
     )
     result = pytester.runpytest()
-    result.stdout.fnmatch_lines(["*1 passed in*"])
+    result.assert_outcomes(passed=1)
     assert result.ret == 0
 
 
@@ -1136,23 +1292,21 @@ def test_collect_with_chdir_during_import(pytester: Pytester) -> None:
     subdir = pytester.mkdir("sub")
     pytester.path.joinpath("conftest.py").write_text(
         textwrap.dedent(
-            """
+            f"""
             import os
-            os.chdir(%r)
+            os.chdir({str(subdir)!r})
             """
-            % (str(subdir),)
-        )
+        ),
+        encoding="utf-8",
     )
     pytester.makepyfile(
-        """
+        f"""
         def test_1():
             import os
-            assert os.getcwd() == %r
+            assert os.getcwd() == {str(subdir)!r}
         """
-        % (str(subdir),)
     )
-    with pytester.path.cwd():
-        result = pytester.runpytest()
+    result = pytester.runpytest()
     result.stdout.fnmatch_lines(["*1 passed in*"])
     assert result.ret == 0
 
@@ -1163,8 +1317,7 @@ def test_1():
         testpaths = .
     """
     )
-    with pytester.path.cwd():
-        result = pytester.runpytest("--collect-only")
+    result = pytester.runpytest("--collect-only")
     result.stdout.fnmatch_lines(["collected 1 item"])
 
 
@@ -1173,8 +1326,12 @@ def test_collect_pyargs_with_testpaths(
 ) -> None:
     testmod = pytester.mkdir("testmod")
     # NOTE: __init__.py is not collected since it does not match python_files.
-    testmod.joinpath("__init__.py").write_text("def test_func(): pass")
-    testmod.joinpath("test_file.py").write_text("def test_func(): pass")
+    testmod.joinpath("__init__.py").write_text(
+        "def test_func(): pass", encoding="utf-8"
+    )
+    testmod.joinpath("test_file.py").write_text(
+        "def test_func(): pass", encoding="utf-8"
+    )
 
     root = pytester.mkdir("root")
     root.joinpath("pytest.ini").write_text(
@@ -1184,12 +1341,64 @@ def test_collect_pyargs_with_testpaths(
         addopts = --pyargs
         testpaths = testmod
     """
-        )
+        ),
+        encoding="utf-8",
     )
     monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep)
-    with root.cwd():
+    with monkeypatch.context() as mp:
+        mp.chdir(root)
         result = pytester.runpytest_subprocess()
-    result.stdout.fnmatch_lines(["*1 passed in*"])
+    result.assert_outcomes(passed=1)
+
+
+def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
+    """The testpaths ini option should load conftests in those paths as 'initial' (#10987)."""
+    p = pytester.mkdir("some_path")
+    p.joinpath("conftest.py").write_text(
+        textwrap.dedent(
+            """
+            def pytest_sessionstart(session):
+                raise Exception("pytest_sessionstart hook successfully run")
+            """
+        ),
+        encoding="utf-8",
+    )
+    pytester.makeini(
+        """
+        [pytest]
+        testpaths = some_path
+        """
+    )
+
+    # No command line args - falls back to testpaths.
+    result = pytester.runpytest()
+    assert result.ret == ExitCode.INTERNAL_ERROR
+    result.stdout.fnmatch_lines(
+        "INTERNALERROR* Exception: pytest_sessionstart hook successfully run"
+    )
+
+    # No fallback.
+    result = pytester.runpytest(".")
+    assert result.ret == ExitCode.NO_TESTS_COLLECTED
+
+
+def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None:
+    """Long option values do not break initial conftests handling (#10169)."""
+    option_value = "x" * 1024 * 1000
+    pytester.makeconftest(
+        """
+        def pytest_addoption(parser):
+            parser.addoption("--xx", default=None)
+        """
+    )
+    pytester.makepyfile(
+        f"""
+        def test_foo(request):
+            assert request.config.getoption("xx") == {option_value!r}
+        """
+    )
+    result = pytester.runpytest(f"--xx={option_value}")
+    assert result.ret == 0
 
 
 def test_collect_symlink_file_arg(pytester: Pytester) -> None:
@@ -1219,13 +1428,14 @@ def test_nodeid(request):
             assert request.node.nodeid == "test_real.py::test_nodeid"
         """
         ),
+        encoding="utf-8",
     )
 
     out_of_tree = pytester.mkdir("out_of_tree")
     symlink_to_sub = out_of_tree.joinpath("symlink_to_sub")
     symlink_or_skip(sub, symlink_to_sub)
     os.chdir(sub)
-    result = pytester.runpytest("-vs", "--rootdir=%s" % sub, symlink_to_sub)
+    result = pytester.runpytest("-vs", f"--rootdir={sub}", symlink_to_sub)
     result.stdout.fnmatch_lines(
         [
             # Should not contain "sub/"!
@@ -1247,12 +1457,16 @@ def test_collect_symlink_dir(pytester: Pytester) -> None:
 def test_collectignore_via_conftest(pytester: Pytester) -> None:
     """collect_ignore in parent conftest skips importing child (issue #4592)."""
     tests = pytester.mkpydir("tests")
-    tests.joinpath("conftest.py").write_text("collect_ignore = ['ignore_me']")
+    tests.joinpath("conftest.py").write_text(
+        "collect_ignore = ['ignore_me']", encoding="utf-8"
+    )
 
     ignore_me = tests.joinpath("ignore_me")
     ignore_me.mkdir()
     ignore_me.joinpath("__init__.py").touch()
-    ignore_me.joinpath("conftest.py").write_text("assert 0, 'should_not_be_called'")
+    ignore_me.joinpath("conftest.py").write_text(
+        "assert 0, 'should_not_be_called'", encoding="utf-8"
+    )
 
     result = pytester.runpytest()
     assert result.ret == ExitCode.NO_TESTS_COLLECTED
@@ -1261,23 +1475,31 @@ def test_collectignore_via_conftest(pytester: Pytester) -> None:
 def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None:
     subdir = pytester.mkdir("sub")
     init = subdir.joinpath("__init__.py")
-    init.write_text("def test_init(): pass")
+    init.write_text("def test_init(): pass", encoding="utf-8")
     p = subdir.joinpath("test_file.py")
-    p.write_text("def test_file(): pass")
+    p.write_text("def test_file(): pass", encoding="utf-8")
 
-    # NOTE: without "-o python_files=*.py" this collects test_file.py twice.
-    # This changed/broke with "Add package scoped fixtures #2283" (2b1410895)
-    # initially (causing a RecursionError).
-    result = pytester.runpytest("-v", str(init), str(p))
+    # Just the package directory, the __init__.py module is filtered out.
+    result = pytester.runpytest("-v", subdir)
     result.stdout.fnmatch_lines(
         [
             "sub/test_file.py::test_file PASSED*",
+            "*1 passed in*",
+        ]
+    )
+
+    # But it's included if specified directly.
+    result = pytester.runpytest("-v", init, p)
+    result.stdout.fnmatch_lines(
+        [
+            "sub/__init__.py::test_init PASSED*",
             "sub/test_file.py::test_file PASSED*",
             "*2 passed in*",
         ]
     )
 
-    result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init), str(p))
+    # Or if the pattern allows it.
+    result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir)
     result.stdout.fnmatch_lines(
         [
             "sub/__init__.py::test_init PASSED*",
@@ -1290,12 +1512,15 @@ def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None:
 def test_collect_pkg_init_only(pytester: Pytester) -> None:
     subdir = pytester.mkdir("sub")
     init = subdir.joinpath("__init__.py")
-    init.write_text("def test_init(): pass")
+    init.write_text("def test_init(): pass", encoding="utf-8")
 
-    result = pytester.runpytest(str(init))
+    result = pytester.runpytest(subdir)
     result.stdout.fnmatch_lines(["*no tests ran in*"])
 
-    result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init))
+    result = pytester.runpytest("-v", init)
+    result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
+
+    result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir)
     result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"])
 
 
@@ -1305,7 +1530,7 @@ def test_collect_sub_with_symlinks(use_pkg: bool, pytester: Pytester) -> None:
     sub = pytester.mkdir("sub")
     if use_pkg:
         sub.joinpath("__init__.py").touch()
-    sub.joinpath("test_file.py").write_text("def test_file(): pass")
+    sub.joinpath("test_file.py").write_text("def test_file(): pass", encoding="utf-8")
 
     # Create a broken symlink.
     symlink_or_skip("test_doesnotexist.py", sub.joinpath("test_broken.py"))
@@ -1343,7 +1568,7 @@ def test_collector_respects_tbstyle(pytester: Pytester) -> None:
 def test_does_not_eagerly_collect_packages(pytester: Pytester) -> None:
     pytester.makepyfile("def test(): pass")
     pydir = pytester.mkpydir("foopkg")
-    pydir.joinpath("__init__.py").write_text("assert False")
+    pydir.joinpath("__init__.py").write_text("assert False", encoding="utf-8")
     result = pytester.runpytest()
     assert result.ret == ExitCode.OK
 
@@ -1372,13 +1597,16 @@ def __init__(self, *k, x, **kw):
             super().__init__(*k, **kw)
             self.x = x
 
+        def collect(self):
+            raise NotImplementedError()
+
     collector = MyCollector.from_parent(
         parent=request.session, path=pytester.path / "foo", x=10
     )
     assert collector.x == 10
 
 
-def test_class_from_parent(pytester: Pytester, request: FixtureRequest) -> None:
+def test_class_from_parent(request: FixtureRequest) -> None:
     """Ensure Class.from_parent can forward custom arguments to the constructor."""
 
     class MyCollector(pytest.Class):
@@ -1419,13 +1647,11 @@ def test_conftest(self, pytester: Pytester) -> None:
         pytester.makepyfile(
             **{
                 "tests/conftest.py": "",
-                "tests/test_foo.py": """
+                "tests/test_foo.py": f"""
                 import sys
                 def test_check():
                     assert r"{tests_dir}" not in sys.path
-                """.format(
-                    tests_dir=tests_dir
-                ),
+                """,
             }
         )
         result = pytester.runpytest("-v", "--import-mode=importlib")
@@ -1470,6 +1696,35 @@ def test_modules_not_importable_as_side_effect(self, pytester: Pytester) -> None
             ]
         )
 
+    def test_using_python_path(self, pytester: Pytester) -> None:
+        """
+        Dummy modules created by insert_missing_modules should not get in
+        the way of modules that could be imported via python path (#9645).
+        """
+        pytester.makeini(
+            """
+            [pytest]
+            pythonpath = .
+            addopts = --import-mode importlib
+            """
+        )
+        pytester.makepyfile(
+            **{
+                "tests/__init__.py": "",
+                "tests/conftest.py": "",
+                "tests/subpath/__init__.py": "",
+                "tests/subpath/helper.py": "",
+                "tests/subpath/test_something.py": """
+                import tests.subpath.helper
+
+                def test_something():
+                    assert True
+                """,
+            }
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines("*1 passed in*")
+
 
 def test_does_not_crash_on_error_from_decorated_function(pytester: Pytester) -> None:
     """Regression test for an issue around bad exception formatting due to
@@ -1497,3 +1752,146 @@ def test_foo(): assert True
 
     assert result.ret == ExitCode.OK
     assert result.parseoutcomes() == {"passed": 1}
+
+
+@pytest.mark.skipif(not sys.platform.startswith("win"), reason="Windows only")
+def test_collect_short_file_windows(pytester: Pytester) -> None:
+    """Reproducer for #11895: short paths not collected on Windows."""
+    short_path = tempfile.mkdtemp()
+    if "~" not in short_path:  # pragma: no cover
+        if running_on_ci():
+            # On CI, we are expecting that under the current GitHub actions configuration,
+            # tempfile.mkdtemp() is producing short paths, so we want to fail to prevent
+            # this from silently changing without us noticing.
+            pytest.fail(
+                f"tempfile.mkdtemp() failed to produce a short path on CI: {short_path}"
+            )
+        else:
+            # We want to skip failing this test locally in this situation because
+            # depending on the local configuration tempfile.mkdtemp() might not produce a short path:
+            # For example, user might have configured %TEMP% exactly to avoid generating short paths.
+            pytest.skip(
+                f"tempfile.mkdtemp() failed to produce a short path: {short_path}, skipping"
+            )
+
+    test_file = Path(short_path).joinpath("test_collect_short_file_windows.py")
+    test_file.write_text("def test(): pass", encoding="UTF-8")
+    result = pytester.runpytest(short_path)
+    assert result.parseoutcomes() == {"passed": 1}
+
+
+def test_pyargs_collection_tree(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
+    """When using `--pyargs`, the collection tree of a pyargs collection
+    argument should only include parents in the import path, not up to confcutdir.
+
+    Regression test for #11904.
+    """
+    site_packages = pytester.path / "venv/lib/site-packages"
+    site_packages.mkdir(parents=True)
+    monkeypatch.syspath_prepend(site_packages)
+    pytester.makepyfile(
+        **{
+            "venv/lib/site-packages/pkg/__init__.py": "",
+            "venv/lib/site-packages/pkg/sub/__init__.py": "",
+            "venv/lib/site-packages/pkg/sub/test_it.py": "def test(): pass",
+        }
+    )
+
+    result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it")
+    assert result.ret == ExitCode.OK
+    result.stdout.fnmatch_lines(
+        [
+            "<Package venv/lib/site-packages/pkg>",
+            "  <Package sub>",
+            "    <Module test_it.py>",
+            "      <Function test>",
+        ],
+        consecutive=True,
+    )
+
+    # Now with an unrelated rootdir with unrelated files.
+    monkeypatch.chdir(tempfile.gettempdir())
+
+    result = pytester.runpytest("--pyargs", "--collect-only", "pkg.sub.test_it")
+    assert result.ret == ExitCode.OK
+    result.stdout.fnmatch_lines(
+        [
+            "<Package *pkg>",
+            "  <Package sub>",
+            "    <Module test_it.py>",
+            "      <Function test>",
+        ],
+        consecutive=True,
+    )
+
+
+def test_do_not_collect_symlink_siblings(
+    pytester: Pytester, tmp_path: Path, request: pytest.FixtureRequest
+) -> None:
+    """
+    Regression test for #12039: Do not collect from directories that are symlinks to other directories in the same path.
+
+    The check for short paths under Windows via os.path.samefile, introduced in #11936, also finds the symlinked
+    directory created by tmp_path/tmpdir.
+    """
+    # Use tmp_path because it creates a symlink with the name "current" next to the directory it creates.
+    symlink_path = tmp_path.parent / (tmp_path.name[:-1] + "current")
+    assert symlink_path.is_symlink() is True
+
+    # Create test file.
+    tmp_path.joinpath("test_foo.py").write_text("def test(): pass", encoding="UTF-8")
+
+    # Ensure we collect it only once if we pass the tmp_path.
+    result = pytester.runpytest(tmp_path, "-sv")
+    result.assert_outcomes(passed=1)
+
+    # Ensure we collect it only once if we pass the symlinked directory.
+    result = pytester.runpytest(symlink_path, "-sv")
+    result.assert_outcomes(passed=1)
+
+
+@pytest.mark.parametrize(
+    "exception_class, msg",
+    [
+        (KeyboardInterrupt, "*!!! KeyboardInterrupt !!!*"),
+        (SystemExit, "INTERNALERROR> SystemExit"),
+    ],
+)
+def test_respect_system_exceptions(
+    pytester: Pytester,
+    exception_class: type[BaseException],
+    msg: str,
+):
+    head = "Before exception"
+    tail = "After exception"
+    ensure_file(pytester.path / "test_eggs.py").write_text(
+        f"print('{head}')", encoding="UTF-8"
+    )
+    ensure_file(pytester.path / "test_ham.py").write_text(
+        f"raise {exception_class.__name__}()", encoding="UTF-8"
+    )
+    ensure_file(pytester.path / "test_spam.py").write_text(
+        f"print('{tail}')", encoding="UTF-8"
+    )
+
+    result = pytester.runpytest_subprocess("-s")
+    result.stdout.fnmatch_lines([f"*{head}*"])
+    result.stdout.fnmatch_lines([msg])
+    result.stdout.no_fnmatch_line(f"*{tail}*")
+
+
+def test_yield_disallowed_in_tests(pytester: Pytester):
+    """Ensure generator test functions with 'yield' fail collection (#12960)."""
+    pytester.makepyfile(
+        """
+        def test_with_yield():
+            yield 1
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == 2
+    result.stdout.fnmatch_lines(
+        ["*'yield' keyword is allowed in fixtures, but not in tests (test_with_yield)*"]
+    )
+    # Assert that no tests were collected
+    result.stdout.fnmatch_lines(["*collected 0 items*"])
diff --git a/testing/test_compat.py b/testing/test_compat.py
index 9f48a31d689..3722bfcfb40 100644
--- a/testing/test_compat.py
+++ b/testing/test_compat.py
@@ -1,35 +1,24 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import enum
+from functools import cached_property
 from functools import partial
 from functools import wraps
 from typing import TYPE_CHECKING
-from typing import Union
 
-import pytest
-from _pytest.compat import _PytestWrapper
 from _pytest.compat import assert_never
-from _pytest.compat import cached_property
 from _pytest.compat import get_real_func
-from _pytest.compat import is_generator
 from _pytest.compat import safe_getattr
 from _pytest.compat import safe_isclass
 from _pytest.outcomes import OutcomeException
-from _pytest.pytester import Pytester
+import pytest
+
 
 if TYPE_CHECKING:
     from typing_extensions import Literal
 
 
-def test_is_generator() -> None:
-    def zap():
-        yield  # pragma: no cover
-
-    def foo():
-        pass  # pragma: no cover
-
-    assert is_generator(zap)
-    assert not is_generator(foo)
-
-
 def test_real_func_loop_limit() -> None:
     class Evil:
         def __init__(self):
@@ -48,10 +37,7 @@ def __getattr__(self, attr):
 
     with pytest.raises(
         ValueError,
-        match=(
-            "could not find real function of <Evil left=800>\n"
-            "stopped at <Evil left=800>"
-        ),
+        match=("wrapper loop when unwrapping <Evil left=998>"),
     ):
         get_real_func(evil)
 
@@ -75,10 +61,13 @@ def func():
     wrapped_func2 = decorator(decorator(wrapped_func))
     assert get_real_func(wrapped_func2) is func
 
-    # special case for __pytest_wrapped__ attribute: used to obtain the function up until the point
-    # a function was wrapped by pytest itself
-    wrapped_func2.__pytest_wrapped__ = _PytestWrapper(wrapped_func)
-    assert get_real_func(wrapped_func2) is wrapped_func
+    # obtain the function up until the point a function was wrapped by pytest itself
+    @pytest.fixture
+    def wrapped_func3():
+        pass  # pragma: no cover
+
+    wrapped_func4 = decorator(wrapped_func3)
+    assert get_real_func(wrapped_func4) is wrapped_func3._get_wrapped_function()
 
 
 def test_get_real_func_partial() -> None:
@@ -91,64 +80,6 @@ def foo(x):
     assert get_real_func(partial(foo)) is foo
 
 
-def test_is_generator_asyncio(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        from _pytest.compat import is_generator
-        import asyncio
-        @asyncio.coroutine
-        def baz():
-            yield from [1,2,3]
-
-        def test_is_generator_asyncio():
-            assert not is_generator(baz)
-    """
-    )
-    # avoid importing asyncio into pytest's own process,
-    # which in turn imports logging (#8)
-    result = pytester.runpytest_subprocess()
-    result.stdout.fnmatch_lines(["*1 passed*"])
-
-
-def test_is_generator_async_syntax(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        from _pytest.compat import is_generator
-        def test_is_generator_py35():
-            async def foo():
-                await foo()
-
-            async def bar():
-                pass
-
-            assert not is_generator(foo)
-            assert not is_generator(bar)
-    """
-    )
-    result = pytester.runpytest()
-    result.stdout.fnmatch_lines(["*1 passed*"])
-
-
-def test_is_generator_async_gen_syntax(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        from _pytest.compat import is_generator
-        def test_is_generator_py36():
-            async def foo():
-                yield
-                await foo()
-
-            async def bar():
-                yield
-
-            assert not is_generator(foo)
-            assert not is_generator(bar)
-    """
-    )
-    result = pytester.runpytest()
-    result.stdout.fnmatch_lines(["*1 passed*"])
-
-
 class ErrorsHelper:
     @property
     def raise_baseexception(self):
@@ -156,26 +87,26 @@ def raise_baseexception(self):
 
     @property
     def raise_exception(self):
-        raise Exception("exception should be catched")
+        raise Exception("exception should be caught")
 
     @property
     def raise_fail_outcome(self):
-        pytest.fail("fail should be catched")
+        pytest.fail("fail should be caught")
 
 
 def test_helper_failures() -> None:
     helper = ErrorsHelper()
-    with pytest.raises(Exception):
-        helper.raise_exception
+    with pytest.raises(Exception):  # noqa: B017
+        _ = helper.raise_exception
     with pytest.raises(OutcomeException):
-        helper.raise_fail_outcome
+        _ = helper.raise_fail_outcome
 
 
 def test_safe_getattr() -> None:
     helper = ErrorsHelper()
     assert safe_getattr(helper, "raise_exception", "default") == "default"
     assert safe_getattr(helper, "raise_fail_outcome", "default") == "default"
-    with pytest.raises(BaseException):
+    with pytest.raises(BaseException):  # noqa: B017
         assert safe_getattr(helper, "raise_baseexception", "default")
 
 
@@ -212,7 +143,7 @@ def prop(self) -> int:
 
 
 def test_assert_never_union() -> None:
-    x: Union[int, str] = 10
+    x: int | str = 10
 
     if isinstance(x, int):
         pass
diff --git a/testing/test_config.py b/testing/test_config.py
index 4435591164f..bb08c40fef4 100644
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -1,20 +1,18 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Sequence
+import dataclasses
+import importlib.metadata
 import os
+from pathlib import Path
+import platform
 import re
 import sys
 import textwrap
-from pathlib import Path
-from typing import Dict
-from typing import List
-from typing import Sequence
-from typing import Tuple
-from typing import Type
-from typing import Union
-
-import attr
+from typing import Any
 
 import _pytest._code
-import pytest
-from _pytest.compat import importlib_metadata
 from _pytest.config import _get_plugin_specs_as_list
 from _pytest.config import _iter_rewritable_modules
 from _pytest.config import _strtobool
@@ -22,6 +20,8 @@
 from _pytest.config import ConftestImportFailure
 from _pytest.config import ExitCode
 from _pytest.config import parse_warning_filter
+from _pytest.config.argparsing import get_ini_default_for_type
+from _pytest.config.argparsing import Parser
 from _pytest.config.exceptions import UsageError
 from _pytest.config.findpaths import determine_setup
 from _pytest.config.findpaths import get_common_ancestor
@@ -29,6 +29,7 @@
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pathlib import absolutepath
 from _pytest.pytester import Pytester
+import pytest
 
 
 class TestParseIni:
@@ -48,16 +49,14 @@ def test_getcfg_and_config(
         monkeypatch.chdir(sub)
         (tmp_path / filename).write_text(
             textwrap.dedent(
-                """\
+                f"""\
                 [{section}]
                 name = value
-                """.format(
-                    section=section
-                )
+                """
             ),
             encoding="utf-8",
         )
-        _, _, cfg = locate_config([sub])
+        _, _, cfg = locate_config(Path.cwd(), [sub])
         assert cfg["name"] == "value"
         config = pytester.parseconfigure(str(sub))
         assert config.inicfg["name"] == "value"
@@ -66,16 +65,15 @@ def test_setupcfg_uses_toolpytest_with_pytest(self, pytester: Pytester) -> None:
         p1 = pytester.makepyfile("def test(): pass")
         pytester.makefile(
             ".cfg",
-            setup="""
+            setup=f"""
                 [tool:pytest]
-                testpaths=%s
+                testpaths={p1.name}
                 [pytest]
                 testpaths=ignored
-        """
-            % p1.name,
+        """,
         )
         result = pytester.runpytest()
-        result.stdout.fnmatch_lines(["*, configfile: setup.cfg, *", "* 1 passed in *"])
+        result.stdout.fnmatch_lines(["configfile: setup.cfg", "* 1 passed in *"])
         assert result.ret == 0
 
     def test_append_parse_args(
@@ -88,7 +86,8 @@ def test_append_parse_args(
                 [pytest]
                 addopts = --verbose
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         config = pytester.parseconfig(tmp_path)
         assert config.option.color == "no"
@@ -112,32 +111,66 @@ def test_tox_ini_wrong_version(self, pytester: Pytester) -> None:
 
     @pytest.mark.parametrize(
         "section, name",
-        [("tool:pytest", "setup.cfg"), ("pytest", "tox.ini"), ("pytest", "pytest.ini")],
+        [
+            ("tool:pytest", "setup.cfg"),
+            ("pytest", "tox.ini"),
+            ("pytest", "pytest.ini"),
+            ("pytest", ".pytest.ini"),
+        ],
     )
     def test_ini_names(self, pytester: Pytester, name, section) -> None:
         pytester.path.joinpath(name).write_text(
             textwrap.dedent(
-                """
+                f"""
             [{section}]
-            minversion = 1.0
-        """.format(
-                    section=section
-                )
-            )
+            minversion = 3.36
+        """
+            ),
+            encoding="utf-8",
         )
         config = pytester.parseconfig()
-        assert config.getini("minversion") == "1.0"
+        assert config.getini("minversion") == "3.36"
 
     def test_pyproject_toml(self, pytester: Pytester) -> None:
-        pytester.makepyprojecttoml(
+        pyproject_toml = pytester.makepyprojecttoml(
             """
             [tool.pytest.ini_options]
             minversion = "1.0"
         """
         )
         config = pytester.parseconfig()
+        assert config.inipath == pyproject_toml
         assert config.getini("minversion") == "1.0"
 
+    def test_empty_pyproject_toml(self, pytester: Pytester) -> None:
+        """An empty pyproject.toml is considered as config if no other option is found."""
+        pyproject_toml = pytester.makepyprojecttoml("")
+        config = pytester.parseconfig()
+        assert config.inipath == pyproject_toml
+
+    def test_empty_pyproject_toml_found_many(self, pytester: Pytester) -> None:
+        """
+        In case we find multiple pyproject.toml files in our search, without a [tool.pytest.ini_options]
+        table and without finding other candidates, the closest to where we started wins.
+        """
+        pytester.makefile(
+            ".toml",
+            **{
+                "pyproject": "",
+                "foo/pyproject": "",
+                "foo/bar/pyproject": "",
+            },
+        )
+        config = pytester.parseconfig(pytester.path / "foo/bar")
+        assert config.inipath == pytester.path / "foo/bar/pyproject.toml"
+
+    def test_pytest_ini_trumps_pyproject_toml(self, pytester: Pytester) -> None:
+        """A pytest.ini always take precedence over a pyproject.toml file."""
+        pytester.makepyprojecttoml("[tool.pytest.ini_options]")
+        pytest_ini = pytester.makefile(".ini", pytest="")
+        config = pytester.parseconfig()
+        assert config.inipath == pytest_ini
+
     def test_toxini_before_lower_pytestini(self, pytester: Pytester) -> None:
         sub = pytester.mkdir("sub")
         sub.joinpath("tox.ini").write_text(
@@ -146,7 +179,8 @@ def test_toxini_before_lower_pytestini(self, pytester: Pytester) -> None:
             [pytest]
             minversion = 2.0
         """
-            )
+            ),
+            encoding="utf-8",
         )
         pytester.path.joinpath("pytest.ini").write_text(
             textwrap.dedent(
@@ -154,16 +188,46 @@ def test_toxini_before_lower_pytestini(self, pytester: Pytester) -> None:
             [pytest]
             minversion = 1.5
         """
-            )
+            ),
+            encoding="utf-8",
         )
         config = pytester.parseconfigure(sub)
         assert config.getini("minversion") == "2.0"
 
     def test_ini_parse_error(self, pytester: Pytester) -> None:
-        pytester.path.joinpath("pytest.ini").write_text("addopts = -x")
+        pytester.path.joinpath("pytest.ini").write_text(
+            "addopts = -x", encoding="utf-8"
+        )
+        result = pytester.runpytest()
+        assert result.ret != 0
+        result.stderr.fnmatch_lines("ERROR: *pytest.ini:1: no section header defined")
+
+    def test_toml_parse_error(self, pytester: Pytester) -> None:
+        pytester.makepyprojecttoml(
+            """
+            \\"
+            """
+        )
         result = pytester.runpytest()
         assert result.ret != 0
-        result.stderr.fnmatch_lines(["ERROR: *pytest.ini:1: no section header defined"])
+        result.stderr.fnmatch_lines("ERROR: *pyproject.toml: Invalid statement*")
+
+    def test_confcutdir_default_without_configfile(self, pytester: Pytester) -> None:
+        # If --confcutdir is not specified, and there is no configfile, default
+        # to the rootpath.
+        sub = pytester.mkdir("sub")
+        os.chdir(sub)
+        config = pytester.parseconfigure()
+        assert config.pluginmanager._confcutdir == sub
+
+    def test_confcutdir_default_with_configfile(self, pytester: Pytester) -> None:
+        # If --confcutdir is not specified, and there is a configfile, default
+        # to the configfile's directory.
+        pytester.makeini("[pytest]")
+        sub = pytester.mkdir("sub")
+        os.chdir(sub)
+        config = pytester.parseconfigure()
+        assert config.pluginmanager._confcutdir == pytester.path
 
     @pytest.mark.xfail(reason="probably not needed")
     def test_confcutdir(self, pytester: Pytester) -> None:
@@ -386,7 +450,7 @@ def pytest_configure(config):
             pytest.param(
                 """
                 [some_other_header]
-                required_plugins = wont be triggered
+                required_plugins = won't be triggered
                 [pytest]
                 """,
                 "1.5",
@@ -408,11 +472,11 @@ def test_missing_required_plugins(
         This test installs a mock "myplugin-1.5" which is used in the parametrized test cases.
         """
 
-        @attr.s
+        @dataclasses.dataclass
         class DummyEntryPoint:
-            name = attr.ib()
-            module = attr.ib()
-            group = "pytest11"
+            name: str
+            module: str
+            group: str = "pytest11"
 
             def load(self):
                 __import__(self.module)
@@ -422,11 +486,11 @@ def load(self):
             DummyEntryPoint("myplugin1", "myplugin1_module"),
         ]
 
-        @attr.s
+        @dataclasses.dataclass
         class DummyDist:
-            entry_points = attr.ib()
-            files = ()
-            version = plugin_version
+            entry_points: object
+            files: object = ()
+            version: str = plugin_version
 
             @property
             def metadata(self):
@@ -438,7 +502,7 @@ def my_dists():
         pytester.makepyfile(myplugin1_module="# my plugin module")
         pytester.syspathinsert()
 
-        monkeypatch.setattr(importlib_metadata, "distributions", my_dists)
+        monkeypatch.setattr(importlib.metadata, "distributions", my_dists)
         monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
 
         pytester.makeini(ini_file_text)
@@ -470,6 +534,24 @@ def pytest_load_initial_conftests(early_config, parser, args):
         result = pytester.runpytest("--foo=1")
         result.stdout.fnmatch_lines("* no tests ran in *")
 
+    def test_args_source_args(self, pytester: Pytester):
+        config = pytester.parseconfig("--", "test_filename.py")
+        assert config.args_source == Config.ArgsSource.ARGS
+
+    def test_args_source_invocation_dir(self, pytester: Pytester):
+        config = pytester.parseconfig()
+        assert config.args_source == Config.ArgsSource.INVOCATION_DIR
+
+    def test_args_source_testpaths(self, pytester: Pytester):
+        pytester.makeini(
+            """
+            [pytest]
+            testpaths=*
+        """
+        )
+        config = pytester.parseconfig()
+        assert config.args_source == Config.ArgsSource.TESTPATHS
+
 
 class TestConfigCmdlineParsing:
     def test_parsing_again_fails(self, pytester: Pytester) -> None:
@@ -500,6 +582,8 @@ def pytest_addoption(parser):
         )
         config = pytester.parseconfig("-c", "custom.ini")
         assert config.getini("custom") == "1"
+        config = pytester.parseconfig("--config-file", "custom.ini")
+        assert config.getini("custom") == "1"
 
         pytester.makefile(
             ".cfg",
@@ -510,6 +594,8 @@ def pytest_addoption(parser):
         )
         config = pytester.parseconfig("-c", "custom_tool_pytest_section.cfg")
         assert config.getini("custom") == "1"
+        config = pytester.parseconfig("--config-file", "custom_tool_pytest_section.cfg")
+        assert config.getini("custom") == "1"
 
         pytester.makefile(
             ".toml",
@@ -522,6 +608,8 @@ def pytest_addoption(parser):
         )
         config = pytester.parseconfig("-c", "custom.toml")
         assert config.getini("custom") == "1"
+        config = pytester.parseconfig("--config-file", "custom.toml")
+        assert config.getini("custom") == "1"
 
     def test_absolute_win32_path(self, pytester: Pytester) -> None:
         temp_ini_file = pytester.makefile(
@@ -536,18 +624,20 @@ def test_absolute_win32_path(self, pytester: Pytester) -> None:
         temp_ini_file_norm = normpath(str(temp_ini_file))
         ret = pytest.main(["-c", temp_ini_file_norm])
         assert ret == ExitCode.OK
+        ret = pytest.main(["--config-file", temp_ini_file_norm])
+        assert ret == ExitCode.OK
 
 
 class TestConfigAPI:
     def test_config_trace(self, pytester: Pytester) -> None:
         config = pytester.parseconfig()
-        values: List[str] = []
+        values: list[str] = []
         config.trace.root.setwriter(values.append)
         config.trace("hello")
         assert len(values) == 1
         assert values[0] == "hello [config]\n"
 
-    def test_config_getoption(self, pytester: Pytester) -> None:
+    def test_config_getoption_declared_option_name(self, pytester: Pytester) -> None:
         pytester.makeconftest(
             """
             def pytest_addoption(parser):
@@ -559,6 +649,18 @@ def pytest_addoption(parser):
             assert config.getoption(x) == "this"
         pytest.raises(ValueError, config.getoption, "qweqwe")
 
+        config_novalue = pytester.parseconfig()
+        assert config_novalue.getoption("hello") is None
+        assert config_novalue.getoption("hello", default=1) is None
+        assert config_novalue.getoption("hello", default=1, skip=True) == 1
+
+    def test_config_getoption_undeclared_option_name(self, pytester: Pytester) -> None:
+        config = pytester.parseconfig()
+        with pytest.raises(ValueError):
+            config.getoption("x")
+        assert config.getoption("x", default=1) == 1
+        assert config.getoption("x", default=1, skip=True) == 1
+
     def test_config_getoption_unicode(self, pytester: Pytester) -> None:
         pytester.makeconftest(
             """
@@ -586,29 +688,16 @@ def pytest_addoption(parser):
         with pytest.raises(pytest.skip.Exception):
             config.getvalueorskip("hello")
 
-    def test_getoption(self, pytester: Pytester) -> None:
-        config = pytester.parseconfig()
-        with pytest.raises(ValueError):
-            config.getvalue("x")
-        assert config.getoption("x", 1) == 1
-
     def test_getconftest_pathlist(self, pytester: Pytester, tmp_path: Path) -> None:
         somepath = tmp_path.joinpath("x", "y", "z")
         p = tmp_path.joinpath("conftest.py")
-        p.write_text(f"mylist = {['.', str(somepath)]}")
+        p.write_text(f"mylist = {['.', str(somepath)]}", encoding="utf-8")
         config = pytester.parseconfigure(p)
-        assert (
-            config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path)
-            is None
-        )
-        pl = (
-            config._getconftest_pathlist("mylist", path=tmp_path, rootpath=tmp_path)
-            or []
-        )
-        print(pl)
-        assert len(pl) == 2
-        assert pl[0] == tmp_path
-        assert pl[1] == somepath
+        assert config._getconftest_pathlist("notexist", path=tmp_path) is None
+        assert config._getconftest_pathlist("mylist", path=tmp_path) == [
+            tmp_path,
+            somepath,
+        ]
 
     @pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"'])
     def test_addini(self, pytester: Pytester, maybe_type: str) -> None:
@@ -752,15 +841,90 @@ def pytest_addoption(parser):
         )
         if str_val != "no-ini":
             pytester.makeini(
-                """
+                f"""
                 [pytest]
-                strip=%s
+                strip={str_val}
             """
-                % str_val
             )
         config = pytester.parseconfig()
         assert config.getini("strip") is bool_val
 
+    @pytest.mark.parametrize("str_val, int_val", [("10", 10), ("no-ini", 2)])
+    def test_addini_int(self, pytester: Pytester, str_val: str, int_val: bool) -> None:
+        pytester.makeconftest(
+            """
+            def pytest_addoption(parser):
+                parser.addini("ini_param", "", type="int", default=2)
+        """
+        )
+        if str_val != "no-ini":
+            pytester.makeini(
+                f"""
+                [pytest]
+                ini_param={str_val}
+            """
+            )
+        config = pytester.parseconfig()
+        assert config.getini("ini_param") == int_val
+
+    def test_addini_int_invalid(self, pytester: Pytester) -> None:
+        pytester.makeconftest(
+            """
+            def pytest_addoption(parser):
+                parser.addini("ini_param", "", type="int", default=2)
+        """
+        )
+        pytester.makepyprojecttoml(
+            """
+            [tool.pytest.ini_options]
+            ini_param=["foo"]
+            """
+        )
+        config = pytester.parseconfig()
+        with pytest.raises(
+            TypeError, match="Expected an int string for option ini_param"
+        ):
+            _ = config.getini("ini_param")
+
+    @pytest.mark.parametrize("str_val, float_val", [("10.5", 10.5), ("no-ini", 2.2)])
+    def test_addini_float(
+        self, pytester: Pytester, str_val: str, float_val: bool
+    ) -> None:
+        pytester.makeconftest(
+            """
+            def pytest_addoption(parser):
+                parser.addini("ini_param", "", type="float", default=2.2)
+        """
+        )
+        if str_val != "no-ini":
+            pytester.makeini(
+                f"""
+                [pytest]
+                ini_param={str_val}
+            """
+            )
+        config = pytester.parseconfig()
+        assert config.getini("ini_param") == float_val
+
+    def test_addini_float_invalid(self, pytester: Pytester) -> None:
+        pytester.makeconftest(
+            """
+            def pytest_addoption(parser):
+                parser.addini("ini_param", "", type="float", default=2.2)
+        """
+        )
+        pytester.makepyprojecttoml(
+            """
+            [tool.pytest.ini_options]
+            ini_param=["foo"]
+            """
+        )
+        config = pytester.parseconfig()
+        with pytest.raises(
+            TypeError, match="Expected a float string for option ini_param"
+        ):
+            _ = config.getini("ini_param")
+
     def test_addinivalue_line_existing(self, pytester: Pytester) -> None:
         pytester.makeconftest(
             """
@@ -801,13 +965,74 @@ def pytest_addoption(parser):
         assert len(values) == 2
         assert values == ["456", "123"]
 
+    def test_addini_default_values(self, pytester: Pytester) -> None:
+        """Tests the default values for configuration based on
+        config type
+        """
+        pytester.makeconftest(
+            """
+            def pytest_addoption(parser):
+                parser.addini("linelist1", "", type="linelist")
+                parser.addini("paths1", "", type="paths")
+                parser.addini("pathlist1", "", type="pathlist")
+                parser.addini("args1", "", type="args")
+                parser.addini("bool1", "", type="bool")
+                parser.addini("string1", "", type="string")
+                parser.addini("none_1", "", type="linelist", default=None)
+                parser.addini("none_2", "", default=None)
+                parser.addini("no_type", "")
+        """
+        )
+
+        config = pytester.parseconfig()
+        # default for linelist, paths, pathlist and args is []
+        value = config.getini("linelist1")
+        assert value == []
+        value = config.getini("paths1")
+        assert value == []
+        value = config.getini("pathlist1")
+        assert value == []
+        value = config.getini("args1")
+        assert value == []
+        # default for bool is False
+        value = config.getini("bool1")
+        assert value is False
+        # default for string is ""
+        value = config.getini("string1")
+        assert value == ""
+        # should return None if None is explicitly set as default value
+        # irrespective of the type argument
+        value = config.getini("none_1")
+        assert value is None
+        value = config.getini("none_2")
+        assert value is None
+        # in case no type is provided and no default set
+        # treat it as string and default value will be ""
+        value = config.getini("no_type")
+        assert value == ""
+
+    @pytest.mark.parametrize(
+        "type, expected",
+        [
+            pytest.param(None, "", id="None"),
+            pytest.param("string", "", id="string"),
+            pytest.param("paths", [], id="paths"),
+            pytest.param("pathlist", [], id="pathlist"),
+            pytest.param("args", [], id="args"),
+            pytest.param("linelist", [], id="linelist"),
+            pytest.param("bool", False, id="bool"),
+        ],
+    )
+    def test_get_ini_default_for_type(self, type: Any, expected: Any) -> None:
+        assert get_ini_default_for_type(type) == expected
+
     def test_confcutdir_check_isdir(self, pytester: Pytester) -> None:
         """Give an error if --confcutdir is not a valid directory (#2078)"""
         exp_match = r"^--confcutdir must be a directory, given: "
         with pytest.raises(pytest.UsageError, match=exp_match):
             pytester.parseconfig("--confcutdir", pytester.path.joinpath("file"))
         with pytest.raises(pytest.UsageError, match=exp_match):
-            pytester.parseconfig("--confcutdir", pytester.path.joinpath("inexistant"))
+            pytester.parseconfig("--confcutdir", pytester.path.joinpath("nonexistent"))
 
         p = pytester.mkdir("dir")
         config = pytester.parseconfig("--confcutdir", p)
@@ -827,11 +1052,45 @@ def test_confcutdir_check_isdir(self, pytester: Pytester) -> None:
             (["src/bar/__init__.py"], ["bar"]),
             (["src/bar/__init__.py", "setup.py"], ["bar"]),
             (["source/python/bar/__init__.py", "setup.py"], ["bar"]),
+            # editable installation finder modules
+            (["__editable___xyz_finder.py"], []),
+            (["bar/__init__.py", "__editable___xyz_finder.py"], ["bar"]),
         ],
     )
     def test_iter_rewritable_modules(self, names, expected) -> None:
         assert list(_iter_rewritable_modules(names)) == expected
 
+    def test_add_cleanup(self, pytester: Pytester) -> None:
+        config = Config.fromdictargs({}, [])
+        config._do_configure()
+        report = []
+
+        class MyError(BaseException):
+            pass
+
+        @config.add_cleanup
+        def cleanup_last():
+            report.append("cleanup_last")
+
+        @config.add_cleanup
+        def raise_2():
+            report.append("raise_2")
+            raise MyError("raise_2")
+
+        @config.add_cleanup
+        def raise_1():
+            report.append("raise_1")
+            raise MyError("raise_1")
+
+        @config.add_cleanup
+        def cleanup_first():
+            report.append("cleanup_first")
+
+        with pytest.raises(MyError, match=r"raise_2"):
+            config._ensure_unconfigure()
+
+        assert report == ["cleanup_first", "raise_1", "raise_2", "cleanup_last"]
+
 
 class TestConfigFromdictargs:
     def test_basic_behavior(self, _sys_snapshot) -> None:
@@ -848,7 +1107,7 @@ def test_basic_behavior(self, _sys_snapshot) -> None:
 
     def test_invocation_params_args(self, _sys_snapshot) -> None:
         """Show that fromdictargs can handle args in their "orig" format"""
-        option_dict: Dict[str, object] = {}
+        option_dict: dict[str, object] = {}
         args = ["-vvvv", "-s", "a", "b"]
 
         config = Config.fromdictargs(option_dict, args)
@@ -868,7 +1127,8 @@ def test_inifilename(self, tmp_path: Path) -> None:
                 [pytest]
                 name = value
                 """
-            )
+            ),
+            encoding="utf-8",
         )
 
         inifilename = "../../foo/bar.ini"
@@ -885,7 +1145,8 @@ def test_inifilename(self, tmp_path: Path) -> None:
                 name = wrong-value
                 should_not_be_set = true
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         with MonkeyPatch.context() as mp:
             mp.chdir(cwd)
@@ -953,7 +1214,7 @@ class Dist:
     def my_dists():
         return (Dist,)
 
-    monkeypatch.setattr(importlib_metadata, "distributions", my_dists)
+    monkeypatch.setattr(importlib.metadata, "distributions", my_dists)
     pytester.makeconftest(
         """
         pytest_plugins = "mytestplugin",
@@ -986,7 +1247,7 @@ class Distribution:
     def distributions():
         return (Distribution(),)
 
-    monkeypatch.setattr(importlib_metadata, "distributions", distributions)
+    monkeypatch.setattr(importlib.metadata, "distributions", distributions)
     with pytest.raises(ImportError):
         pytester.parseconfig()
 
@@ -1013,7 +1274,7 @@ class Distribution:
     def distributions():
         return (Distribution(),)
 
-    monkeypatch.setattr(importlib_metadata, "distributions", distributions)
+    monkeypatch.setattr(importlib.metadata, "distributions", distributions)
     pytester.parseconfig()
 
 
@@ -1041,7 +1302,7 @@ class Distribution:
     def distributions():
         return (Distribution(),)
 
-    monkeypatch.setattr(importlib_metadata, "distributions", distributions)
+    monkeypatch.setattr(importlib.metadata, "distributions", distributions)
     args = ("-p", "no:mytestplugin") if block_it else ()
     config = pytester.parseconfig(*args)
     config.pluginmanager.import_plugin("mytestplugin")
@@ -1054,14 +1315,13 @@ def distributions():
         )
 
 
-@pytest.mark.parametrize(
-    "parse_args,should_load", [(("-p", "mytestplugin"), True), ((), False)]
-)
+@pytest.mark.parametrize("disable_plugin_method", ["env_var", "flag", ""])
+@pytest.mark.parametrize("enable_plugin_method", ["env_var", "flag", ""])
 def test_disable_plugin_autoload(
     pytester: Pytester,
     monkeypatch: MonkeyPatch,
-    parse_args: Union[Tuple[str, str], Tuple[()]],
-    should_load: bool,
+    enable_plugin_method: str,
+    disable_plugin_method: str,
 ) -> None:
     class DummyEntryPoint:
         project_name = name = "mytestplugin"
@@ -1082,23 +1342,60 @@ class PseudoPlugin:
         attrs_used = []
 
         def __getattr__(self, name):
-            assert name == "__loader__"
+            assert name in ("__loader__", "__spec__")
             self.attrs_used.append(name)
             return object()
 
     def distributions():
         return (Distribution(),)
 
-    monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
-    monkeypatch.setattr(importlib_metadata, "distributions", distributions)
-    monkeypatch.setitem(sys.modules, "mytestplugin", PseudoPlugin())  # type: ignore[misc]
+    parse_args: list[str] = []
+
+    if disable_plugin_method == "env_var":
+        monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
+    elif disable_plugin_method == "flag":
+        monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
+        parse_args.append("--disable-plugin-autoload")
+    else:
+        assert disable_plugin_method == ""
+        monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
+
+    if enable_plugin_method == "env_var":
+        monkeypatch.setenv("PYTEST_PLUGINS", "mytestplugin")
+    elif enable_plugin_method == "flag":
+        parse_args.extend(["-p", "mytestplugin"])
+    else:
+        assert enable_plugin_method == ""
+
+    monkeypatch.setattr(importlib.metadata, "distributions", distributions)
+    monkeypatch.setitem(sys.modules, "mytestplugin", PseudoPlugin())
     config = pytester.parseconfig(*parse_args)
+
     has_loaded = config.pluginmanager.get_plugin("mytestplugin") is not None
-    assert has_loaded == should_load
-    if should_load:
-        assert PseudoPlugin.attrs_used == ["__loader__"]
-    else:
-        assert PseudoPlugin.attrs_used == []
+    # it should load if it's enabled, or we haven't disabled autoloading
+    assert has_loaded == (bool(enable_plugin_method) or not disable_plugin_method)
+
+    # The reason for the discrepancy between 'has_loaded' and __loader__ being accessed
+    # appears to be the monkeypatching of importlib.metadata.distributions; where
+    # files being empty means that _mark_plugins_for_rewrite doesn't find the plugin.
+    # But enable_method==flag ends up in mark_rewrite being called and __loader__
+    # being accessed.
+    assert ("__loader__" in PseudoPlugin.attrs_used) == (
+        has_loaded
+        and not (enable_plugin_method in ("env_var", "") and not disable_plugin_method)
+    )
+
+    # __spec__ is accessed in AssertionRewritingHook.exec_module, which would be
+    # eventually called if we did a full pytest run; but it's only accessed with
+    # enable_plugin_method=="env_var" because that will early-load it.
+    # Except when autoloads aren't disabled, in which case PytestPluginManager.import_plugin
+    # bails out before importing it.. because it knows it'll be loaded later?
+    # The above seems a bit weird, but I *think* it's true.
+    if platform.python_implementation() != "PyPy":
+        assert ("__spec__" in PseudoPlugin.attrs_used) == bool(
+            enable_plugin_method == "env_var" and disable_plugin_method
+        )
+    # __spec__ is present when testing locally on pypy, but not in CI ????
 
 
 def test_plugin_loading_order(pytester: Pytester) -> None:
@@ -1109,8 +1406,7 @@ def test_terminal_plugin(request):
             import myplugin
             assert myplugin.terminal_plugin == [False, True]
         """,
-        **{
-            "myplugin": """
+        myplugin="""
             terminal_plugin = []
 
             def pytest_configure(config):
@@ -1119,25 +1415,13 @@ def pytest_configure(config):
             def pytest_sessionstart(session):
                 config = session.config
                 terminal_plugin.append(bool(config.pluginmanager.get_plugin("terminalreporter")))
-            """
-        },
+            """,
     )
     pytester.syspathinsert()
     result = pytester.runpytest("-p", "myplugin", str(p1))
     assert result.ret == 0
 
 
-def test_cmdline_processargs_simple(pytester: Pytester) -> None:
-    pytester.makeconftest(
-        """
-        def pytest_cmdline_preparse(args):
-            args.append("-h")
-    """
-    )
-    result = pytester.runpytest()
-    result.stdout.fnmatch_lines(["*pytest*", "*-h*"])
-
-
 def test_invalid_options_show_extra_information(pytester: Pytester) -> None:
     """Display extra information when pytest exits due to unrecognized
     options in the command-line."""
@@ -1151,8 +1435,8 @@ def test_invalid_options_show_extra_information(pytester: Pytester) -> None:
     result.stderr.fnmatch_lines(
         [
             "*error: unrecognized arguments: --invalid-option*",
-            "*  inifile: %s*" % pytester.path.joinpath("tox.ini"),
-            "*  rootdir: %s*" % pytester.path,
+            "*  inifile: {}*".format(pytester.path.joinpath("tox.ini")),
+            f"*  rootdir: {pytester.path}*",
         ]
     )
 
@@ -1167,7 +1451,7 @@ def test_invalid_options_show_extra_information(pytester: Pytester) -> None:
     ],
 )
 def test_consider_args_after_options_for_rootdir(
-    pytester: Pytester, args: List[str]
+    pytester: Pytester, args: list[str]
 ) -> None:
     """
     Consider all arguments in the command-line for rootdir
@@ -1264,22 +1548,27 @@ def pytest_load_initial_conftests(self):
     m = My()
     pm.register(m)
     hc = pm.hook.pytest_load_initial_conftests
-    values = hc._nonwrappers + hc._wrappers
-    expected = [
-        "_pytest.config",
-        m.__module__,
-        "_pytest.pythonpath",
-        "_pytest.capture",
-        "_pytest.warnings",
+    hookimpls = [
+        (
+            hookimpl.function.__module__,
+            "wrapper" if (hookimpl.wrapper or hookimpl.hookwrapper) else "nonwrapper",
+        )
+        for hookimpl in hc.get_hookimpls()
+    ]
+    assert hookimpls == [
+        ("_pytest.config", "nonwrapper"),
+        (m.__module__, "nonwrapper"),
+        ("_pytest.legacypath", "nonwrapper"),
+        ("_pytest.capture", "wrapper"),
+        ("_pytest.warnings", "wrapper"),
     ]
-    assert [x.function.__module__ for x in values] == expected
 
 
 def test_get_plugin_specs_as_list() -> None:
     def exp_match(val: object) -> str:
         return (
-            "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %s"
-            % re.escape(repr(val))
+            f"Plugins may be specified as a sequence or a ','-separated string "
+            f"of plugin names. Got: {re.escape(repr(val))}"
         )
 
     with pytest.raises(pytest.UsageError, match=exp_match({"foo"})):
@@ -1315,16 +1604,16 @@ class pytest_something:
 
 class TestRootdir:
     def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
-        assert get_common_ancestor([tmp_path]) == tmp_path
+        assert get_common_ancestor(Path.cwd(), [tmp_path]) == tmp_path
         a = tmp_path / "a"
         a.mkdir()
-        assert get_common_ancestor([a, tmp_path]) == tmp_path
-        assert get_common_ancestor([tmp_path, a]) == tmp_path
+        assert get_common_ancestor(Path.cwd(), [a, tmp_path]) == tmp_path
+        assert get_common_ancestor(Path.cwd(), [tmp_path, a]) == tmp_path
         monkeypatch.chdir(tmp_path)
-        assert get_common_ancestor([]) == tmp_path
+        assert get_common_ancestor(Path.cwd(), []) == tmp_path
         no_path = tmp_path / "does-not-exist"
-        assert get_common_ancestor([no_path]) == tmp_path
-        assert get_common_ancestor([no_path / "a"]) == tmp_path
+        assert get_common_ancestor(Path.cwd(), [no_path]) == tmp_path
+        assert get_common_ancestor(Path.cwd(), [no_path / "a"]) == tmp_path
 
     @pytest.mark.parametrize(
         "name, contents",
@@ -1339,17 +1628,27 @@ def test_simple_noini(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
     )
     def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None:
         inipath = tmp_path / name
-        inipath.write_text(contents, "utf-8")
+        inipath.write_text(contents, encoding="utf-8")
 
         a = tmp_path / "a"
         a.mkdir()
         b = a / "b"
         b.mkdir()
         for args in ([str(tmp_path)], [str(a)], [str(b)]):
-            rootpath, parsed_inipath, _ = determine_setup(None, args)
+            rootpath, parsed_inipath, _ = determine_setup(
+                inifile=None,
+                args=args,
+                rootdir_cmd_arg=None,
+                invocation_dir=Path.cwd(),
+            )
             assert rootpath == tmp_path
             assert parsed_inipath == inipath
-        rootpath, parsed_inipath, ini_config = determine_setup(None, [str(b), str(a)])
+        rootpath, parsed_inipath, ini_config = determine_setup(
+            inifile=None,
+            args=[str(b), str(a)],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert parsed_inipath == inipath
         assert ini_config == {"x": "10"}
@@ -1361,7 +1660,12 @@ def test_pytestini_overrides_empty_other(self, tmp_path: Path, name: str) -> Non
         a = tmp_path / "a"
         a.mkdir()
         (a / name).touch()
-        rootpath, parsed_inipath, _ = determine_setup(None, [str(a)])
+        rootpath, parsed_inipath, _ = determine_setup(
+            inifile=None,
+            args=[str(a)],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert parsed_inipath == inipath
 
@@ -1370,14 +1674,24 @@ def test_setuppy_fallback(self, tmp_path: Path) -> None:
         a.mkdir()
         (a / "setup.cfg").touch()
         (tmp_path / "setup.py").touch()
-        rootpath, inipath, inicfg = determine_setup(None, [str(a)])
+        rootpath, inipath, inicfg = determine_setup(
+            inifile=None,
+            args=[str(a)],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert inipath is None
         assert inicfg == {}
 
     def test_nothing(self, tmp_path: Path, monkeypatch: MonkeyPatch) -> None:
         monkeypatch.chdir(tmp_path)
-        rootpath, inipath, inicfg = determine_setup(None, [str(tmp_path)])
+        rootpath, inipath, inicfg = determine_setup(
+            inifile=None,
+            args=[str(tmp_path)],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert inipath is None
         assert inicfg == {}
@@ -1398,8 +1712,13 @@ def test_with_specific_inifile(
     ) -> None:
         p = tmp_path / name
         p.touch()
-        p.write_text(contents, "utf-8")
-        rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)])
+        p.write_text(contents, encoding="utf-8")
+        rootpath, inipath, ini_config = determine_setup(
+            inifile=str(p),
+            args=[str(tmp_path)],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert inipath == p
         assert ini_config == {"x": "10"}
@@ -1413,14 +1732,24 @@ def test_explicit_config_file_sets_rootdir(
         monkeypatch.chdir(tmp_path)
 
         # No config file is explicitly given: rootdir is determined to be cwd.
-        rootpath, found_inipath, *_ = determine_setup(None, [str(tests_dir)])
+        rootpath, found_inipath, *_ = determine_setup(
+            inifile=None,
+            args=[str(tests_dir)],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert found_inipath is None
 
         # Config file is explicitly given: rootdir is determined to be inifile's directory.
         inipath = tmp_path / "pytest.ini"
         inipath.touch()
-        rootpath, found_inipath, *_ = determine_setup(str(inipath), [str(tests_dir)])
+        rootpath, found_inipath, *_ = determine_setup(
+            inifile=str(inipath),
+            args=[str(tests_dir)],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert found_inipath == inipath
 
@@ -1432,7 +1761,12 @@ def test_with_arg_outside_cwd_without_inifile(
         a.mkdir()
         b = tmp_path / "b"
         b.mkdir()
-        rootpath, inifile, _ = determine_setup(None, [str(a), str(b)])
+        rootpath, inifile, _ = determine_setup(
+            inifile=None,
+            args=[str(a), str(b)],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert inifile is None
 
@@ -1443,7 +1777,12 @@ def test_with_arg_outside_cwd_with_inifile(self, tmp_path: Path) -> None:
         b.mkdir()
         inipath = a / "pytest.ini"
         inipath.touch()
-        rootpath, parsed_inipath, _ = determine_setup(None, [str(a), str(b)])
+        rootpath, parsed_inipath, _ = determine_setup(
+            inifile=None,
+            args=[str(a), str(b)],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == a
         assert inipath == parsed_inipath
 
@@ -1452,7 +1791,12 @@ def test_with_non_dir_arg(
         self, dirs: Sequence[str], tmp_path: Path, monkeypatch: MonkeyPatch
     ) -> None:
         monkeypatch.chdir(tmp_path)
-        rootpath, inipath, _ = determine_setup(None, dirs)
+        rootpath, inipath, _ = determine_setup(
+            inifile=None,
+            args=dirs,
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert inipath is None
 
@@ -1463,7 +1807,12 @@ def test_with_existing_file_in_subdir(
         a.mkdir()
         (a / "exists").touch()
         monkeypatch.chdir(tmp_path)
-        rootpath, inipath, _ = determine_setup(None, ["a/exist"])
+        rootpath, inipath, _ = determine_setup(
+            inifile=None,
+            args=["a/exist"],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
         assert rootpath == tmp_path
         assert inipath is None
 
@@ -1477,7 +1826,12 @@ def test_with_config_also_in_parent_directory(
         (tmp_path / "myproject" / "tests").mkdir()
         monkeypatch.chdir(tmp_path / "myproject")
 
-        rootpath, inipath, _ = determine_setup(None, ["tests/"])
+        rootpath, inipath, _ = determine_setup(
+            inifile=None,
+            args=["tests/"],
+            rootdir_cmd_arg=None,
+            invocation_dir=Path.cwd(),
+        )
 
         assert rootpath == tmp_path / "myproject"
         assert inipath == tmp_path / "myproject" / "setup.cfg"
@@ -1489,12 +1843,11 @@ def test_override_ini_names(self, pytester: Pytester, name: str) -> None:
         section = "[pytest]" if name != "setup.cfg" else "[tool:pytest]"
         pytester.path.joinpath(name).write_text(
             textwrap.dedent(
-                """
+                f"""
             {section}
-            custom = 1.0""".format(
-                    section=section
-                )
-            )
+            custom = 1.0"""
+            ),
+            encoding="utf-8",
         )
         pytester.makeconftest(
             """
@@ -1531,7 +1884,7 @@ def pytest_addoption(parser):
         )
         pytester.makepyfile(
             r"""
-            def test_overriden(pytestconfig):
+            def test_overridden(pytestconfig):
                 config_paths = pytestconfig.getini("paths")
                 print(config_paths)
                 for cpf in config_paths:
@@ -1628,10 +1981,10 @@ def test_addopts_before_initini(
         self, monkeypatch: MonkeyPatch, _config_for_test, _sys_snapshot
     ) -> None:
         cache_dir = ".custom_cache"
-        monkeypatch.setenv("PYTEST_ADDOPTS", "-o cache_dir=%s" % cache_dir)
+        monkeypatch.setenv("PYTEST_ADDOPTS", f"-o cache_dir={cache_dir}")
         config = _config_for_test
         config._preparse([], addopts=True)
-        assert config._override_ini == ["cache_dir=%s" % cache_dir]
+        assert config._override_ini == [f"cache_dir={cache_dir}"]
 
     def test_addopts_from_env_not_concatenated(
         self, monkeypatch: MonkeyPatch, _config_for_test
@@ -1657,8 +2010,8 @@ def test_addopts_from_ini_not_concatenated(self, pytester: Pytester) -> None:
         result = pytester.runpytest("cache_dir=ignored")
         result.stderr.fnmatch_lines(
             [
-                "%s: error: argument -o/--override-ini: expected one argument (via addopts config)"
-                % (pytester._request.config._parser.optparser.prog,)
+                f"{pytester._request.config._parser.optparser.prog}: error: "
+                f"argument -o/--override-ini: expected one argument (via addopts config)"
             ]
         )
         assert result.ret == _pytest.config.ExitCode.USAGE_ERROR
@@ -1695,6 +2048,18 @@ def test():
         assert "ERROR:" not in result.stderr.str()
         result.stdout.fnmatch_lines(["collected 1 item", "*= 1 passed in *="])
 
+    def test_override_ini_without_config_file(self, pytester: Pytester) -> None:
+        pytester.makepyfile(**{"src/override_ini_without_config_file.py": ""})
+        pytester.makepyfile(
+            **{
+                "tests/test_override_ini_without_config_file.py": (
+                    "import override_ini_without_config_file\ndef test(): pass"
+                ),
+            }
+        )
+        result = pytester.runpytest("--override-ini", "pythonpath=src")
+        assert result.parseoutcomes() == {"passed": 1}
+
 
 def test_help_via_addopts(pytester: Pytester) -> None:
     pytester.makeini(
@@ -1746,8 +2111,8 @@ def pytest_addoption(parser):
     result.stderr.fnmatch_lines(
         [
             "ERROR: usage: *",
-            "%s: error: argument --invalid-option-should-allow-for-help: expected one argument"
-            % (pytester._request.config._parser.optparser.prog,),
+            f"{pytester._request.config._parser.optparser.prog}: error: "
+            f"argument --invalid-option-should-allow-for-help: expected one argument",
         ]
     )
     # Does not display full/default help.
@@ -1785,6 +2150,10 @@ def test_config_does_not_load_blocked_plugin_from_args(pytester: Pytester) -> No
     result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"])
     assert result.ret == ExitCode.USAGE_ERROR
 
+    result = pytester.runpytest(str(p), "-p no:capture", "-s")
+    result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"])
+    assert result.ret == ExitCode.USAGE_ERROR
+
 
 def test_invocation_args(pytester: Pytester) -> None:
     """Ensure that Config.invocation_* arguments are correctly defined"""
@@ -1822,25 +2191,15 @@ class DummyPlugin:
     ],
 )
 def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None:
-    if plugin == "debugging":
-        # Fixed in xdist (after 1.27.0).
-        # https://github.com/pytest-dev/pytest-xdist/pull/422
-        try:
-            import xdist  # noqa: F401
-        except ImportError:
-            pass
-        else:
-            pytest.skip("does not work with xdist currently")
-
     p = pytester.makepyfile("def test(): pass")
-    result = pytester.runpytest(str(p), "-pno:%s" % plugin)
+    result = pytester.runpytest(str(p), f"-pno:{plugin}")
 
     if plugin == "python":
         assert result.ret == ExitCode.USAGE_ERROR
         result.stderr.fnmatch_lines(
             [
                 "ERROR: not found: */test_config_blocked_default_plugins.py",
-                "(no name '*/test_config_blocked_default_plugins.py' in any of [])",
+                "(no match in any of *<Dir *>*",
             ]
         )
         return
@@ -1850,7 +2209,7 @@ def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None
         result.stdout.fnmatch_lines(["* 1 passed in *"])
 
     p = pytester.makepyfile("def test(): assert 0")
-    result = pytester.runpytest(str(p), "-pno:%s" % plugin)
+    result = pytester.runpytest(str(p), f"-pno:{plugin}")
     assert result.ret == ExitCode.TESTS_FAILED
     if plugin != "terminal":
         result.stdout.fnmatch_lines(["* 1 failed in *"])
@@ -1881,6 +2240,9 @@ def test_pytest_custom_cfg_unsupported(self, pytester: Pytester) -> None:
         with pytest.raises(pytest.fail.Exception):
             pytester.runpytest("-c", "custom.cfg")
 
+        with pytest.raises(pytest.fail.Exception):
+            pytester.runpytest("--config-file", "custom.cfg")
+
 
 class TestPytestPluginsVariable:
     def test_pytest_plugins_in_non_top_level_conftest_unsupported(
@@ -1909,7 +2271,6 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs(
         self, pytester: Pytester, use_pyargs: bool
     ) -> None:
         """When using --pyargs, do not emit the warning about non-top-level conftest warnings (#4039, #4044)"""
-
         files = {
             "src/pkg/__init__.py": "",
             "src/pkg/conftest.py": "",
@@ -1924,9 +2285,7 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs(
         args = ("--pyargs", "pkg") if use_pyargs else ()
         res = pytester.runpytest(*args)
         assert res.ret == (0 if use_pyargs else 2)
-        msg = (
-            msg
-        ) = "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported"
+        msg = "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported"
         if use_pyargs:
             assert msg not in res.stdout.str()
         else:
@@ -1989,9 +2348,7 @@ def test_conftest_import_error_repr(tmp_path: Path) -> None:
         try:
             raise RuntimeError("some error")
         except Exception as exc:
-            assert exc.__traceback__ is not None
-            exc_info = (type(exc), exc, exc.__traceback__)
-            raise ConftestImportFailure(path, exc_info) from exc
+            raise ConftestImportFailure(path, cause=exc) from exc
 
 
 def test_strtobool() -> None:
@@ -2030,7 +2387,7 @@ def test_strtobool() -> None:
     ],
 )
 def test_parse_warning_filter(
-    arg: str, escape: bool, expected: Tuple[str, str, Type[Warning], str, int]
+    arg: str, escape: bool, expected: tuple[str, str, type[Warning], str, int]
 ) -> None:
     assert parse_warning_filter(arg, escape=escape) == expected
 
@@ -2102,8 +2459,81 @@ def test_debug_help(self, pytester: Pytester) -> None:
         result = pytester.runpytest("-h")
         result.stdout.fnmatch_lines(
             [
-                "*store internal tracing debug information in this log*",
-                "*This file is opened with 'w' and truncated as a result*",
-                "*Defaults to 'pytestdebug.log'.",
+                "*Store internal tracing debug information in this log*",
+                "*file. This file is opened with 'w' and truncated as a*",
+                "*Default: pytestdebug.log.",
             ]
         )
+
+
+class TestVerbosity:
+    SOME_OUTPUT_TYPE = Config.VERBOSITY_ASSERTIONS
+    SOME_OUTPUT_VERBOSITY_LEVEL = 5
+
+    class VerbosityIni:
+        def pytest_addoption(self, parser: Parser) -> None:
+            Config._add_verbosity_ini(
+                parser, TestVerbosity.SOME_OUTPUT_TYPE, help="some help text"
+            )
+
+    def test_level_matches_verbose_when_not_specified(
+        self, pytester: Pytester, tmp_path: Path
+    ) -> None:
+        tmp_path.joinpath("pytest.ini").write_text(
+            textwrap.dedent(
+                """\
+                [pytest]
+                addopts = --verbose
+                """
+            ),
+            encoding="utf-8",
+        )
+        pytester.plugins = [TestVerbosity.VerbosityIni()]
+
+        config = pytester.parseconfig(tmp_path)
+
+        assert (
+            config.get_verbosity(TestVerbosity.SOME_OUTPUT_TYPE)
+            == config.option.verbose
+        )
+
+    def test_level_matches_verbose_when_not_known_type(
+        self, pytester: Pytester, tmp_path: Path
+    ) -> None:
+        tmp_path.joinpath("pytest.ini").write_text(
+            textwrap.dedent(
+                """\
+                [pytest]
+                addopts = --verbose
+                """
+            ),
+            encoding="utf-8",
+        )
+        pytester.plugins = [TestVerbosity.VerbosityIni()]
+
+        config = pytester.parseconfig(tmp_path)
+
+        assert config.get_verbosity("some fake verbosity type") == config.option.verbose
+
+    def test_level_matches_specified_override(
+        self, pytester: Pytester, tmp_path: Path
+    ) -> None:
+        setting_name = f"verbosity_{TestVerbosity.SOME_OUTPUT_TYPE}"
+        tmp_path.joinpath("pytest.ini").write_text(
+            textwrap.dedent(
+                f"""\
+                [pytest]
+                addopts = --verbose
+                {setting_name} = {TestVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL}
+                """
+            ),
+            encoding="utf-8",
+        )
+        pytester.plugins = [TestVerbosity.VerbosityIni()]
+
+        config = pytester.parseconfig(tmp_path)
+
+        assert (
+            config.get_verbosity(TestVerbosity.SOME_OUTPUT_TYPE)
+            == TestVerbosity.SOME_OUTPUT_VERBOSITY_LEVEL
+        )
diff --git a/testing/test_conftest.py b/testing/test_conftest.py
index 64c1014a533..bd083574ffc 100644
--- a/testing/test_conftest.py
+++ b/testing/test_conftest.py
@@ -1,20 +1,20 @@
-import argparse
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Generator
+from collections.abc import Sequence
 import os
-import textwrap
 from pathlib import Path
+import textwrap
 from typing import cast
-from typing import Dict
-from typing import Generator
-from typing import List
-from typing import Optional
 
-import pytest
 from _pytest.config import ExitCode
 from _pytest.config import PytestPluginManager
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pathlib import symlink_or_skip
 from _pytest.pytester import Pytester
 from _pytest.tmpdir import TempPathFactory
+import pytest
 
 
 def ConftestWithSetinitial(path) -> PytestPluginManager:
@@ -24,30 +24,34 @@ def ConftestWithSetinitial(path) -> PytestPluginManager:
 
 
 def conftest_setinitial(
-    conftest: PytestPluginManager, args, confcutdir: Optional["os.PathLike[str]"] = None
+    conftest: PytestPluginManager,
+    args: Sequence[str | Path],
+    confcutdir: Path | None = None,
 ) -> None:
-    class Namespace:
-        def __init__(self) -> None:
-            self.file_or_dir = args
-            self.confcutdir = os.fspath(confcutdir) if confcutdir is not None else None
-            self.noconftest = False
-            self.pyargs = False
-            self.importmode = "prepend"
-
-    namespace = cast(argparse.Namespace, Namespace())
-    conftest._set_initial_conftests(namespace, rootpath=Path(args[0]))
+    conftest._set_initial_conftests(
+        args=args,
+        pyargs=False,
+        noconftest=False,
+        rootpath=Path(args[0]),
+        confcutdir=confcutdir,
+        invocation_dir=Path.cwd(),
+        importmode="prepend",
+        consider_namespace_packages=False,
+    )
 
 
 @pytest.mark.usefixtures("_sys_snapshot")
 class TestConftestValueAccessGlobal:
     @pytest.fixture(scope="module", params=["global", "inpackage"])
-    def basedir(
-        self, request, tmp_path_factory: TempPathFactory
-    ) -> Generator[Path, None, None]:
+    def basedir(self, request, tmp_path_factory: TempPathFactory) -> Generator[Path]:
         tmp_path = tmp_path_factory.mktemp("basedir", numbered=True)
         tmp_path.joinpath("adir/b").mkdir(parents=True)
-        tmp_path.joinpath("adir/conftest.py").write_text("a=1 ; Directory = 3")
-        tmp_path.joinpath("adir/b/conftest.py").write_text("b=2 ; a = 1.5")
+        tmp_path.joinpath("adir/conftest.py").write_text(
+            "a=1 ; Directory = 3", encoding="utf-8"
+        )
+        tmp_path.joinpath("adir/b/conftest.py").write_text(
+            "b=2 ; a = 1.5", encoding="utf-8"
+        )
         if request.param == "inpackage":
             tmp_path.joinpath("adir/__init__.py").touch()
             tmp_path.joinpath("adir/b/__init__.py").touch()
@@ -57,63 +61,69 @@ def basedir(
     def test_basic_init(self, basedir: Path) -> None:
         conftest = PytestPluginManager()
         p = basedir / "adir"
-        assert (
-            conftest._rget_with_confmod("a", p, importmode="prepend", rootpath=basedir)[
-                1
-            ]
-            == 1
+        conftest._loadconftestmodules(
+            p, importmode="prepend", rootpath=basedir, consider_namespace_packages=False
         )
+        assert conftest._rget_with_confmod("a", p)[1] == 1
 
-    def test_immediate_initialiation_and_incremental_are_the_same(
+    def test_immediate_initialization_and_incremental_are_the_same(
         self, basedir: Path
     ) -> None:
         conftest = PytestPluginManager()
         assert not len(conftest._dirpath2confmods)
-        conftest._getconftestmodules(
-            basedir, importmode="prepend", rootpath=Path(basedir)
+        conftest._loadconftestmodules(
+            basedir,
+            importmode="prepend",
+            rootpath=basedir,
+            consider_namespace_packages=False,
         )
         snap1 = len(conftest._dirpath2confmods)
         assert snap1 == 1
-        conftest._getconftestmodules(
-            basedir / "adir", importmode="prepend", rootpath=basedir
+        conftest._loadconftestmodules(
+            basedir / "adir",
+            importmode="prepend",
+            rootpath=basedir,
+            consider_namespace_packages=False,
         )
         assert len(conftest._dirpath2confmods) == snap1 + 1
-        conftest._getconftestmodules(
-            basedir / "b", importmode="prepend", rootpath=basedir
+        conftest._loadconftestmodules(
+            basedir / "b",
+            importmode="prepend",
+            rootpath=basedir,
+            consider_namespace_packages=False,
         )
         assert len(conftest._dirpath2confmods) == snap1 + 2
 
     def test_value_access_not_existing(self, basedir: Path) -> None:
         conftest = ConftestWithSetinitial(basedir)
         with pytest.raises(KeyError):
-            conftest._rget_with_confmod(
-                "a", basedir, importmode="prepend", rootpath=Path(basedir)
-            )
+            conftest._rget_with_confmod("a", basedir)
 
     def test_value_access_by_path(self, basedir: Path) -> None:
         conftest = ConftestWithSetinitial(basedir)
         adir = basedir / "adir"
-        assert (
-            conftest._rget_with_confmod(
-                "a", adir, importmode="prepend", rootpath=basedir
-            )[1]
-            == 1
+        conftest._loadconftestmodules(
+            adir,
+            importmode="prepend",
+            rootpath=basedir,
+            consider_namespace_packages=False,
         )
-        assert (
-            conftest._rget_with_confmod(
-                "a", adir / "b", importmode="prepend", rootpath=basedir
-            )[1]
-            == 1.5
+        assert conftest._rget_with_confmod("a", adir)[1] == 1
+        conftest._loadconftestmodules(
+            adir / "b",
+            importmode="prepend",
+            rootpath=basedir,
+            consider_namespace_packages=False,
         )
+        assert conftest._rget_with_confmod("a", adir / "b")[1] == 1.5
 
     def test_value_access_with_confmod(self, basedir: Path) -> None:
         startdir = basedir / "adir" / "b"
         startdir.joinpath("xx").mkdir()
         conftest = ConftestWithSetinitial(startdir)
-        mod, value = conftest._rget_with_confmod(
-            "a", startdir, importmode="prepend", rootpath=Path(basedir)
-        )
+        mod, value = conftest._rget_with_confmod("a", startdir)
         assert value == 1.5
+        assert mod.__file__ is not None
         path = Path(mod.__file__)
         assert path.parent == basedir / "adir" / "b"
         assert path.stem == "conftest"
@@ -121,8 +131,12 @@ def test_value_access_with_confmod(self, basedir: Path) -> None:
 
 def test_conftest_in_nonpkg_with_init(tmp_path: Path, _sys_snapshot) -> None:
     tmp_path.joinpath("adir-1.0/b").mkdir(parents=True)
-    tmp_path.joinpath("adir-1.0/conftest.py").write_text("a=1 ; Directory = 3")
-    tmp_path.joinpath("adir-1.0/b/conftest.py").write_text("b=2 ; a = 1.5")
+    tmp_path.joinpath("adir-1.0/conftest.py").write_text(
+        "a=1 ; Directory = 3", encoding="utf-8"
+    )
+    tmp_path.joinpath("adir-1.0/b/conftest.py").write_text(
+        "b=2 ; a = 1.5", encoding="utf-8"
+    )
     tmp_path.joinpath("adir-1.0/b/__init__.py").touch()
     tmp_path.joinpath("adir-1.0/__init__.py").touch()
     ConftestWithSetinitial(tmp_path.joinpath("adir-1.0", "b"))
@@ -133,9 +147,7 @@ def test_doubledash_considered(pytester: Pytester) -> None:
     conf.joinpath("conftest.py").touch()
     conftest = PytestPluginManager()
     conftest_setinitial(conftest, [conf.name, conf.name])
-    values = conftest._getconftestmodules(
-        conf, importmode="prepend", rootpath=pytester.path
-    )
+    values = conftest._getconftestmodules(conf)
     assert len(values) == 1
 
 
@@ -145,10 +157,9 @@ def test_issue151_load_all_conftests(pytester: Pytester) -> None:
         p = pytester.mkdir(name)
         p.joinpath("conftest.py").touch()
 
-    conftest = PytestPluginManager()
-    conftest_setinitial(conftest, names)
-    d = list(conftest._conftestpath2mod.values())
-    assert len(d) == len(names)
+    pm = PytestPluginManager()
+    conftest_setinitial(pm, names)
+    assert len(set(pm.get_plugins()) - {pm}) == len(names)
 
 
 def test_conftest_global_import(pytester: Pytester) -> None:
@@ -159,15 +170,25 @@ def test_conftest_global_import(pytester: Pytester) -> None:
         import pytest
         from _pytest.config import PytestPluginManager
         conf = PytestPluginManager()
-        mod = conf._importconftest(Path("conftest.py"), importmode="prepend", rootpath=Path.cwd())
+        mod = conf._importconftest(
+            Path("conftest.py"),
+            importmode="prepend",
+            rootpath=Path.cwd(),
+            consider_namespace_packages=False,
+        )
         assert mod.x == 3
         import conftest
         assert conftest is mod, (conftest, mod)
         sub = Path("sub")
         sub.mkdir()
         subconf = sub / "conftest.py"
-        subconf.write_text("y=4")
-        mod2 = conf._importconftest(subconf, importmode="prepend", rootpath=Path.cwd())
+        subconf.write_text("y=4", encoding="utf-8")
+        mod2 = conf._importconftest(
+            subconf,
+            importmode="prepend",
+            rootpath=Path.cwd(),
+            consider_namespace_packages=False,
+        )
         assert mod != mod2
         assert mod2.y == 4
         import conftest
@@ -183,26 +204,37 @@ def test_conftestcutdir(pytester: Pytester) -> None:
     p = pytester.mkdir("x")
     conftest = PytestPluginManager()
     conftest_setinitial(conftest, [pytester.path], confcutdir=p)
-    values = conftest._getconftestmodules(
-        p, importmode="prepend", rootpath=pytester.path
+    conftest._loadconftestmodules(
+        p,
+        importmode="prepend",
+        rootpath=pytester.path,
+        consider_namespace_packages=False,
     )
+    values = conftest._getconftestmodules(p)
     assert len(values) == 0
-    values = conftest._getconftestmodules(
-        conf.parent, importmode="prepend", rootpath=pytester.path
+    conftest._loadconftestmodules(
+        conf.parent,
+        importmode="prepend",
+        rootpath=pytester.path,
+        consider_namespace_packages=False,
     )
+    values = conftest._getconftestmodules(conf.parent)
     assert len(values) == 0
-    assert Path(conf) not in conftest._conftestpath2mod
+    assert not conftest.has_plugin(str(conf))
     # but we can still import a conftest directly
-    conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path)
-    values = conftest._getconftestmodules(
-        conf.parent, importmode="prepend", rootpath=pytester.path
+    conftest._importconftest(
+        conf,
+        importmode="prepend",
+        rootpath=pytester.path,
+        consider_namespace_packages=False,
     )
+    values = conftest._getconftestmodules(conf.parent)
+    assert values[0].__file__ is not None
     assert values[0].__file__.startswith(str(conf))
     # and all sub paths get updated properly
-    values = conftest._getconftestmodules(
-        p, importmode="prepend", rootpath=pytester.path
-    )
+    values = conftest._getconftestmodules(p)
     assert len(values) == 1
+    assert values[0].__file__ is not None
     assert values[0].__file__.startswith(str(conf))
 
 
@@ -210,10 +242,9 @@ def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None:
     conf = pytester.makeconftest("")
     conftest = PytestPluginManager()
     conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent)
-    values = conftest._getconftestmodules(
-        conf.parent, importmode="prepend", rootpath=pytester.path
-    )
+    values = conftest._getconftestmodules(conf.parent)
     assert len(values) == 1
+    assert values[0].__file__ is not None
     assert values[0].__file__.startswith(str(conf))
 
 
@@ -222,15 +253,15 @@ def test_setinitial_conftest_subdirs(pytester: Pytester, name: str) -> None:
     sub = pytester.mkdir(name)
     subconftest = sub.joinpath("conftest.py")
     subconftest.touch()
-    conftest = PytestPluginManager()
-    conftest_setinitial(conftest, [sub.parent], confcutdir=pytester.path)
+    pm = PytestPluginManager()
+    conftest_setinitial(pm, [sub.parent], confcutdir=pytester.path)
     key = subconftest.resolve()
     if name not in ("whatever", ".dotdir"):
-        assert key in conftest._conftestpath2mod
-        assert len(conftest._conftestpath2mod) == 1
+        assert pm.has_plugin(str(key))
+        assert len(set(pm.get_plugins()) - {pm}) == 1
     else:
-        assert key not in conftest._conftestpath2mod
-        assert len(conftest._conftestpath2mod) == 0
+        assert not pm.has_plugin(str(key))
+        assert len(set(pm.get_plugins()) - {pm}) == 0
 
 
 def test_conftest_confcutdir(pytester: Pytester) -> None:
@@ -242,13 +273,45 @@ def test_conftest_confcutdir(pytester: Pytester) -> None:
             def pytest_addoption(parser):
                 parser.addoption("--xyz", action="store_true")
             """
-        )
+        ),
+        encoding="utf-8",
     )
-    result = pytester.runpytest("-h", "--confcutdir=%s" % x, x)
+    result = pytester.runpytest("-h", f"--confcutdir={x}", x)
     result.stdout.fnmatch_lines(["*--xyz*"])
     result.stdout.no_fnmatch_line("*warning: could not load initial*")
 
 
+def test_installed_conftest_is_picked_up(pytester: Pytester, tmp_path: Path) -> None:
+    """When using `--pyargs` to run tests in an installed packages (located e.g.
+    in a site-packages in the PYTHONPATH), conftest files in there are picked
+    up.
+
+    Regression test for #9767.
+    """
+    # pytester dir - the source tree.
+    # tmp_path - the simulated site-packages dir (not in source tree).
+
+    pytester.syspathinsert(tmp_path)
+    pytester.makepyprojecttoml("[tool.pytest.ini_options]")
+    tmp_path.joinpath("foo").mkdir()
+    tmp_path.joinpath("foo", "__init__.py").touch()
+    tmp_path.joinpath("foo", "conftest.py").write_text(
+        textwrap.dedent(
+            """\
+            import pytest
+            @pytest.fixture
+            def fix(): return None
+            """
+        ),
+        encoding="utf-8",
+    )
+    tmp_path.joinpath("foo", "test_it.py").write_text(
+        "def test_it(fix): pass", encoding="utf-8"
+    )
+    result = pytester.runpytest("--pyargs", "foo")
+    assert result.ret == 0
+
+
 def test_conftest_symlink(pytester: Pytester) -> None:
     """`conftest.py` discovery follows normal path resolution and does not resolve symlinks."""
     # Structure:
@@ -313,7 +376,7 @@ def fixture():
             """
         ),
     }
-    pytester.makepyfile(**{"real/%s" % k: v for k, v in source.items()})
+    pytester.makepyfile(**{f"real/{k}": v for k, v in source.items()})
 
     # Create a build directory that contains symlinks to actual files
     # but doesn't symlink actual directories.
@@ -329,13 +392,13 @@ def fixture():
 
 @pytest.mark.skipif(
     os.path.normcase("x") != os.path.normcase("X"),
-    reason="only relevant for case insensitive file systems",
+    reason="only relevant for case-insensitive file systems",
 )
 def test_conftest_badcase(pytester: Pytester) -> None:
     """Check conftest.py loading when directory casing is wrong (#5792)."""
     pytester.path.joinpath("JenkinsRoot/test").mkdir(parents=True)
     source = {"setup.py": "", "test/__init__.py": "", "test/conftest.py": ""}
-    pytester.makepyfile(**{"JenkinsRoot/%s" % k: v for k, v in source.items()})
+    pytester.makepyfile(**{f"JenkinsRoot/{k}": v for k, v in source.items()})
 
     os.chdir(pytester.path.joinpath("jenkinsroot/test"))
     result = pytester.runpytest()
@@ -369,7 +432,8 @@ def test_conftest_existing_junitxml(pytester: Pytester) -> None:
             def pytest_addoption(parser):
                 parser.addoption("--xyz", action="store_true")
             """
-        )
+        ),
+        encoding="utf-8",
     )
     pytester.makefile(ext=".xml", junit="")  # Writes junit.xml
     result = pytester.runpytest("-h", "--junitxml", "junit.xml")
@@ -380,18 +444,21 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) ->
     ct1 = pytester.makeconftest("")
     sub = pytester.mkdir("sub")
     ct2 = sub / "conftest.py"
-    ct2.write_text("")
+    ct2.write_text("", encoding="utf-8")
 
-    def impct(p, importmode, root):
+    def impct(p, importmode, root, consider_namespace_packages):
         return p
 
     conftest = PytestPluginManager()
     conftest._confcutdir = pytester.path
     monkeypatch.setattr(conftest, "_importconftest", impct)
-    mods = cast(
-        List[Path],
-        conftest._getconftestmodules(sub, importmode="prepend", rootpath=pytester.path),
+    conftest._loadconftestmodules(
+        sub,
+        importmode="prepend",
+        rootpath=pytester.path,
+        consider_namespace_packages=False,
     )
+    mods = cast(list[Path], conftest._getconftestmodules(sub))
     expected = [ct1, ct2]
     assert mods == expected
 
@@ -418,7 +485,8 @@ def foo():
             def bar(foo):
                 return 'bar'
             """
-        )
+        ),
+        encoding="utf-8",
     )
     subsub = sub.joinpath("subsub")
     subsub.mkdir()
@@ -435,7 +503,8 @@ def bar():
             def test_event_fixture(bar):
                 assert bar == 'sub bar'
             """
-        )
+        ),
+        encoding="utf-8",
     )
     result = pytester.runpytest("sub")
     result.stdout.fnmatch_lines(["*1 passed*"])
@@ -449,10 +518,11 @@ def test_conftest_found_with_double_dash(pytester: Pytester) -> None:
             def pytest_addoption(parser):
                 parser.addoption("--hello-world", action="store_true")
             """
-        )
+        ),
+        encoding="utf-8",
     )
     p = sub.joinpath("test_hello.py")
-    p.write_text("def test_hello(): pass")
+    p.write_text("def test_hello(): pass", encoding="utf-8")
     result = pytester.runpytest(str(p) + "::test_hello", "-h")
     result.stdout.fnmatch_lines(
         """
@@ -462,7 +532,7 @@ def pytest_addoption(parser):
 
 
 class TestConftestVisibility:
-    def _setup_tree(self, pytester: Pytester) -> Dict[str, Path]:  # for issue616
+    def _setup_tree(self, pytester: Pytester) -> dict[str, Path]:  # for issue616
         # example mostly taken from:
         # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html
         runner = pytester.mkdir("empty")
@@ -476,7 +546,8 @@ def _setup_tree(self, pytester: Pytester) -> Dict[str, Path]:  # for issue616
                 def fxtr():
                     return "from-package"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         package.joinpath("test_pkgroot.py").write_text(
             textwrap.dedent(
@@ -484,7 +555,8 @@ def fxtr():
                 def test_pkgroot(fxtr):
                     assert fxtr == "from-package"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
 
         swc = package.joinpath("swc")
@@ -498,7 +570,8 @@ def test_pkgroot(fxtr):
                 def fxtr():
                     return "from-swc"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         swc.joinpath("test_with_conftest.py").write_text(
             textwrap.dedent(
@@ -506,7 +579,8 @@ def fxtr():
                 def test_with_conftest(fxtr):
                     assert fxtr == "from-swc"
                 """
-            )
+            ),
+            encoding="utf-8",
         )
 
         snc = package.joinpath("snc")
@@ -519,10 +593,11 @@ def test_no_conftest(fxtr):
                     assert fxtr == "from-package"   # No local conftest.py, so should
                                                     # use value from parent dir's
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         print("created directory structure:")
-        for x in pytester.path.rglob(""):
+        for x in pytester.path.glob("**/"):
             print("   " + str(x.relative_to(pytester.path)))
 
         return {"runner": runner, "package": package, "swc": swc, "snc": snc}
@@ -559,11 +634,17 @@ def test_parsefactories_relative_node_ids(
     ) -> None:
         """#616"""
         dirs = self._setup_tree(pytester)
-        print("pytest run in cwd: %s" % (dirs[chdir].relative_to(pytester.path)))
-        print("pytestarg        : %s" % testarg)
-        print("expected pass    : %s" % expect_ntests_passed)
+        print(f"pytest run in cwd: {dirs[chdir].relative_to(pytester.path)}")
+        print(f"pytestarg        : {testarg}")
+        print(f"expected pass    : {expect_ntests_passed}")
         os.chdir(dirs[chdir])
-        reprec = pytester.inline_run(testarg, "-q", "--traceconfig")
+        reprec = pytester.inline_run(
+            testarg,
+            "-q",
+            "--traceconfig",
+            "--confcutdir",
+            pytester.path,
+        )
         reprec.assertoutcome(passed=expect_ntests_passed)
 
 
@@ -579,7 +660,7 @@ def test_search_conftest_up_to_inifile(
     root = pytester.path
     src = root.joinpath("src")
     src.mkdir()
-    src.joinpath("pytest.ini").write_text("[pytest]")
+    src.joinpath("pytest.ini").write_text("[pytest]", encoding="utf-8")
     src.joinpath("conftest.py").write_text(
         textwrap.dedent(
             """\
@@ -587,7 +668,8 @@ def test_search_conftest_up_to_inifile(
             @pytest.fixture
             def fix1(): pass
             """
-        )
+        ),
+        encoding="utf-8",
     )
     src.joinpath("test_foo.py").write_text(
         textwrap.dedent(
@@ -597,7 +679,8 @@ def test_1(fix1):
             def test_2(out_of_reach):
                 pass
             """
-        )
+        ),
+        encoding="utf-8",
     )
     root.joinpath("conftest.py").write_text(
         textwrap.dedent(
@@ -606,18 +689,19 @@ def test_2(out_of_reach):
             @pytest.fixture
             def out_of_reach(): pass
             """
-        )
+        ),
+        encoding="utf-8",
     )
 
     args = [str(src)]
     if confcutdir:
-        args = ["--confcutdir=%s" % root.joinpath(confcutdir)]
+        args = [f"--confcutdir={root.joinpath(confcutdir)}"]
     result = pytester.runpytest(*args)
     match = ""
     if passed:
-        match += "*%d passed*" % passed
+        match += f"*{passed} passed*"
     if error:
-        match += "*%d error*" % error
+        match += f"*{error} error*"
     result.stdout.fnmatch_lines(match)
 
 
@@ -689,7 +773,8 @@ def test_required_option_help(pytester: Pytester) -> None:
             def pytest_addoption(parser):
                 parser.addoption("--xyz", action="store_true", required=True)
             """
-        )
+        ),
+        encoding="utf-8",
     )
     result = pytester.runpytest("-h", x)
     result.stdout.no_fnmatch_line("*argument --xyz is required*")
diff --git a/testing/test_debugging.py b/testing/test_debugging.py
index a822bb57f58..45883568b11 100644
--- a/testing/test_debugging.py
+++ b/testing/test_debugging.py
@@ -1,23 +1,13 @@
-import os
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import sys
-from typing import List
 
 import _pytest._code
-import pytest
 from _pytest.debugging import _validate_usepdb_cls
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
-
-try:
-    # Type ignored for Python <= 3.6.
-    breakpoint  # type: ignore
-except NameError:
-    SUPPORTS_BREAKPOINT_BUILTIN = False
-else:
-    SUPPORTS_BREAKPOINT_BUILTIN = True
-
-
-_ENVIRON_PYTHONBREAKPOINT = os.environ.get("PYTHONBREAKPOINT", "")
+import pytest
 
 
 @pytest.fixture(autouse=True)
@@ -28,16 +18,25 @@ def pdb_env(request):
         pytester._monkeypatch.setenv("PDBPP_HIJACK_PDB", "0")
 
 
-def runpdb_and_get_report(pytester: Pytester, source: str):
+def runpdb(pytester: Pytester, source: str):
     p = pytester.makepyfile(source)
-    result = pytester.runpytest_inprocess("--pdb", p)
-    reports = result.reprec.getreports("pytest_runtest_logreport")  # type: ignore[attr-defined]
+    return pytester.runpytest_inprocess("--pdb", p)
+
+
+def runpdb_and_get_stdout(pytester: Pytester, source: str):
+    result = runpdb(pytester, source)
+    return result.stdout.str()
+
+
+def runpdb_and_get_report(pytester: Pytester, source: str):
+    result = runpdb(pytester, source)
+    reports = result.reprec.getreports("pytest_runtest_logreport")
     assert len(reports) == 3, reports  # setup/call/teardown
     return reports[1]
 
 
 @pytest.fixture
-def custom_pdb_calls() -> List[str]:
+def custom_pdb_calls() -> list[str]:
     called = []
 
     # install dummy debugger class and track which methods were called on it
@@ -53,6 +52,16 @@ def reset(self):
         def interaction(self, *args):
             called.append("interaction")
 
+        # Methods which we copy docstrings to.
+        def do_debug(self, *args):  # pragma: no cover
+            pass
+
+        def do_continue(self, *args):  # pragma: no cover
+            pass
+
+        def do_quit(self, *args):  # pragma: no cover
+            pass
+
     _pytest._CustomPdb = _CustomPdb  # type: ignore
     return called
 
@@ -76,6 +85,16 @@ def set_trace(self, frame):
             print("**CustomDebugger**")
             called.append("set_trace")
 
+        # Methods which we copy docstrings to.
+        def do_debug(self, *args):  # pragma: no cover
+            pass
+
+        def do_continue(self, *args):  # pragma: no cover
+            pass
+
+        def do_quit(self, *args):  # pragma: no cover
+            pass
+
     _pytest._CustomDebugger = _CustomDebugger  # type: ignore
     yield called
     del _pytest._CustomDebugger  # type: ignore
@@ -104,7 +123,10 @@ def test_func():
         )
         assert rep.failed
         assert len(pdblist) == 1
-        tb = _pytest._code.Traceback(pdblist[0][0])
+        if sys.version_info < (3, 13):
+            tb = _pytest._code.Traceback(pdblist[0][0])
+        else:
+            tb = _pytest._code.Traceback(pdblist[0][0].__traceback__)
         assert tb[-1].name == "test_func"
 
     def test_pdb_on_xfail(self, pytester: Pytester, pdblist) -> None:
@@ -132,6 +154,16 @@ def test_func():
         assert rep.skipped
         assert len(pdblist) == 0
 
+    def test_pdb_on_top_level_raise_skiptest(self, pytester, pdblist) -> None:
+        stdout = runpdb_and_get_stdout(
+            pytester,
+            """
+            import unittest
+            raise unittest.SkipTest("This is a common way to skip an entire file.")
+        """,
+        )
+        assert "entering PDB" not in stdout, stdout
+
     def test_pdb_on_BdbQuit(self, pytester, pdblist) -> None:
         rep = runpdb_and_get_report(
             pytester,
@@ -213,7 +245,7 @@ def test_not_called_due_to_quit():
                 pass
         """
         )
-        child = pytester.spawn_pytest("--pdb %s" % p1)
+        child = pytester.spawn_pytest(f"--pdb {p1}")
         child.expect("captured stdout")
         child.expect("get rekt")
         child.expect("captured stderr")
@@ -238,7 +270,7 @@ def test_1():
                 assert False
         """
         )
-        child = pytester.spawn_pytest("--pdb %s" % p1)
+        child = pytester.spawn_pytest(f"--pdb {p1}")
         child.expect("Pdb")
         output = child.before.decode("utf8")
         child.sendeof()
@@ -252,7 +284,7 @@ def test_pdb_print_captured_logs(self, pytester, showcapture: str) -> None:
             """
             def test_1():
                 import logging
-                logging.warn("get " + "rekt")
+                logging.warning("get " + "rekt")
                 assert False
         """
         )
@@ -271,11 +303,11 @@ def test_pdb_print_captured_logs_nologging(self, pytester: Pytester) -> None:
             """
             def test_1():
                 import logging
-                logging.warn("get " + "rekt")
+                logging.warning("get " + "rekt")
                 assert False
         """
         )
-        child = pytester.spawn_pytest("--show-capture=all --pdb -p no:logging %s" % p1)
+        child = pytester.spawn_pytest(f"--show-capture=all --pdb -p no:logging {p1}")
         child.expect("get rekt")
         output = child.before.decode("utf8")
         assert "captured log" not in output
@@ -295,7 +327,7 @@ def test_1():
                 pytest.raises(ValueError, globalfunc)
         """
         )
-        child = pytester.spawn_pytest("--pdb %s" % p1)
+        child = pytester.spawn_pytest(f"--pdb {p1}")
         child.expect(".*def test_1")
         child.expect(".*pytest.raises.*globalfunc")
         child.expect("Pdb")
@@ -312,7 +344,7 @@ def test_pdb_interaction_on_collection_issue181(self, pytester: Pytester) -> Non
             xxx
         """
         )
-        child = pytester.spawn_pytest("--pdb %s" % p1)
+        child = pytester.spawn_pytest(f"--pdb {p1}")
         # child.expect(".*import pytest.*")
         child.expect("Pdb")
         child.sendline("c")
@@ -327,7 +359,7 @@ def pytest_runtest_protocol():
         """
         )
         p1 = pytester.makepyfile("def test_func(): pass")
-        child = pytester.spawn_pytest("--pdb %s" % p1)
+        child = pytester.spawn_pytest(f"--pdb {p1}")
         child.expect("Pdb")
 
         # INTERNALERROR is only displayed once via terminal reporter.
@@ -361,6 +393,7 @@ def test_pdb_prevent_ConftestImportFailure_hiding_exception(
         result = pytester.runpytest_subprocess("--pdb", ".")
         result.stdout.fnmatch_lines(["-> import unknown"])
 
+    @pytest.mark.xfail(reason="#10042", strict=False)
     def test_pdb_interaction_capturing_simple(self, pytester: Pytester) -> None:
         p1 = pytester.makepyfile(
             """
@@ -452,7 +485,7 @@ def test_1(capsys, caplog):
                 assert 0
         """
         )
-        child = pytester.spawn_pytest("--pdb %s" % str(p1))
+        child = pytester.spawn_pytest(f"--pdb {p1!s}")
         child.send("caplog.record_tuples\n")
         child.expect_exact(
             "[('test_pdb_with_caplog_on_pdb_invocation', 30, 'some_warning')]"
@@ -492,7 +525,7 @@ def function_1():
                 '''
         """
         )
-        child = pytester.spawn_pytest("--doctest-modules --pdb %s" % p1)
+        child = pytester.spawn_pytest(f"--doctest-modules --pdb {p1}")
         child.expect("Pdb")
 
         assert "UNEXPECTED EXCEPTION: AssertionError()" in child.before.decode("utf8")
@@ -519,7 +552,7 @@ def function_1():
         )
         # NOTE: does not use pytest.set_trace, but Python's patched pdb,
         #       therefore "-s" is required.
-        child = pytester.spawn_pytest("--doctest-modules --pdb -s %s" % p1)
+        child = pytester.spawn_pytest(f"--doctest-modules --pdb -s {p1}")
         child.expect("Pdb")
         child.sendline("q")
         rest = child.read().decode("utf8")
@@ -529,6 +562,7 @@ def function_1():
         assert "BdbQuit" not in rest
         assert "UNEXPECTED EXCEPTION" not in rest
 
+    @pytest.mark.xfail(reason="#10042", strict=False)
     def test_pdb_interaction_capturing_twice(self, pytester: Pytester) -> None:
         p1 = pytester.makepyfile(
             """
@@ -564,6 +598,7 @@ def test_1():
         assert "1 failed" in rest
         self.flush(child)
 
+    @pytest.mark.xfail(reason="#10042", strict=False)
     def test_pdb_with_injected_do_debug(self, pytester: Pytester) -> None:
         """Simulates pdbpp, which injects Pdb into do_debug, and uses
         self.__class__ in do_continue.
@@ -610,7 +645,7 @@ def test_1():
                 pytest.fail("expected_failure")
         """
         )
-        child = pytester.spawn_pytest("--pdbcls=mytest:CustomPdb %s" % str(p1))
+        child = pytester.spawn_pytest(f"--pdbcls=mytest:CustomPdb {p1!s}")
         child.expect(r"PDB set_trace \(IO-capturing turned off\)")
         child.expect(r"\n\(Pdb")
         child.sendline("debug foo()")
@@ -647,7 +682,7 @@ def test_1():
                 pytest.set_trace()
         """
         )
-        child = pytester.spawn_pytest("-s %s" % p1)
+        child = pytester.spawn_pytest(f"-s {p1}")
         child.expect(r">>> PDB set_trace >>>")
         child.expect("Pdb")
         child.sendline("c")
@@ -756,9 +791,13 @@ def test_pdb_used_outside_test(self, pytester: Pytester) -> None:
             x = 5
         """
         )
+        if sys.version_info[:2] >= (3, 13):
+            break_line = "pytest.set_trace()"
+        else:
+            break_line = "x = 5"
         child = pytester.spawn(f"{sys.executable} {p1}")
-        child.expect("x = 5")
-        child.expect("Pdb")
+        child.expect_exact(break_line)
+        child.expect_exact("Pdb")
         child.sendeof()
         self.flush(child)
 
@@ -773,9 +812,13 @@ def test_foo(a):
                 pass
         """
         )
+        if sys.version_info[:2] >= (3, 13):
+            break_line = "pytest.set_trace()"
+        else:
+            break_line = "x = 5"
         child = pytester.spawn_pytest(str(p1))
-        child.expect("x = 5")
-        child.expect("Pdb")
+        child.expect_exact(break_line)
+        child.expect_exact("Pdb")
         child.sendeof()
         self.flush(child)
 
@@ -843,7 +886,7 @@ def test_post_mortem():
         self.flush(child)
 
     def test_pdb_custom_cls(
-        self, pytester: Pytester, custom_pdb_calls: List[str]
+        self, pytester: Pytester, custom_pdb_calls: list[str]
     ) -> None:
         p1 = pytester.makepyfile("""xxx """)
         result = pytester.runpytest_inprocess(
@@ -869,7 +912,7 @@ def test_pdb_validate_usepdb_cls(self):
         assert _validate_usepdb_cls("pdb:DoesNotExist") == ("pdb", "DoesNotExist")
 
     def test_pdb_custom_cls_without_pdb(
-        self, pytester: Pytester, custom_pdb_calls: List[str]
+        self, pytester: Pytester, custom_pdb_calls: list[str]
     ) -> None:
         p1 = pytester.makepyfile("""xxx """)
         result = pytester.runpytest_inprocess("--pdbcls=_pytest:_CustomPdb", p1)
@@ -903,22 +946,75 @@ def test_foo():
         """
         )
         monkeypatch.setenv("PYTHONPATH", str(pytester.path))
-        child = pytester.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1))
+        child = pytester.spawn_pytest(f"--pdbcls=custom_pdb:CustomPdb {p1!s}")
 
         child.expect("__init__")
         child.expect("custom set_trace>")
         self.flush(child)
 
-
-class TestDebuggingBreakpoints:
-    def test_supports_breakpoint_module_global(self) -> None:
-        """Test that supports breakpoint global marks on Python 3.7+."""
-        if sys.version_info >= (3, 7):
-            assert SUPPORTS_BREAKPOINT_BUILTIN is True
-
     @pytest.mark.skipif(
-        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
+        sys.version_info < (3, 13),
+        reason="Navigating exception chains was introduced in 3.13",
     )
+    def test_pdb_exception_chain_navigation(self, pytester: Pytester) -> None:
+        p1 = pytester.makepyfile(
+            """
+            def inner_raise():
+                is_inner = True
+                raise RuntimeError("Woops")
+
+            def outer_raise():
+                is_inner = False
+                try:
+                    inner_raise()
+                except RuntimeError:
+                    raise RuntimeError("Woopsie")
+
+            def test_1():
+                outer_raise()
+                assert True
+        """
+        )
+        child = pytester.spawn_pytest(f"--pdb {p1}")
+        child.expect("Pdb")
+        child.sendline("is_inner")
+        child.expect_exact("False")
+        child.sendline("exceptions 0")
+        child.sendline("is_inner")
+        child.expect_exact("True")
+        child.sendeof()
+        self.flush(child)
+
+    def test_pdb_wrapped_commands_docstrings(self, pytester: Pytester) -> None:
+        p1 = pytester.makepyfile(
+            """
+            def test_1():
+                assert False
+            """
+        )
+
+        child = pytester.spawn_pytest(f"--pdb {p1}")
+        child.expect("Pdb")
+
+        # Verify no undocumented commands
+        child.sendline("help")
+        child.expect("Documented commands")
+        assert "Undocumented commands" not in child.before.decode()
+
+        child.sendline("help continue")
+        child.expect("Continue execution")
+        child.expect("Pdb")
+
+        child.sendline("help debug")
+        child.expect("Enter a recursive debugger")
+        child.expect("Pdb")
+
+        child.sendline("c")
+        child.sendeof()
+        self.flush(child)
+
+
+class TestDebuggingBreakpoints:
     @pytest.mark.parametrize("arg", ["--pdb", ""])
     def test_sys_breakpointhook_configure_and_unconfigure(
         self, pytester: Pytester, arg: str
@@ -952,10 +1048,10 @@ def test_nothing(): pass
         result = pytester.runpytest_subprocess(*args)
         result.stdout.fnmatch_lines(["*1 passed in *"])
 
-    @pytest.mark.skipif(
-        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
-    )
-    def test_pdb_custom_cls(self, pytester: Pytester, custom_debugger_hook) -> None:
+    def test_pdb_custom_cls(
+        self, pytester: Pytester, custom_debugger_hook, monkeypatch: MonkeyPatch
+    ) -> None:
+        monkeypatch.delenv("PYTHONBREAKPOINT", raising=False)
         p1 = pytester.makepyfile(
             """
             def test_nothing():
@@ -969,9 +1065,6 @@ def test_nothing():
         assert custom_debugger_hook == ["init", "set_trace"]
 
     @pytest.mark.parametrize("arg", ["--pdb", ""])
-    @pytest.mark.skipif(
-        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
-    )
     def test_environ_custom_class(
         self, pytester: Pytester, custom_debugger_hook, arg: str
     ) -> None:
@@ -1002,14 +1095,10 @@ def test_nothing(): pass
         result = pytester.runpytest_subprocess(*args)
         result.stdout.fnmatch_lines(["*1 passed in *"])
 
-    @pytest.mark.skipif(
-        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
-    )
-    @pytest.mark.skipif(
-        not _ENVIRON_PYTHONBREAKPOINT == "",
-        reason="Requires breakpoint() default value",
-    )
-    def test_sys_breakpoint_interception(self, pytester: Pytester) -> None:
+    def test_sys_breakpoint_interception(
+        self, pytester: Pytester, monkeypatch: MonkeyPatch
+    ) -> None:
+        monkeypatch.delenv("PYTHONBREAKPOINT", raising=False)
         p1 = pytester.makepyfile(
             """
             def test_1():
@@ -1025,9 +1114,7 @@ def test_1():
         assert "reading from stdin while output" not in rest
         TestPDB.flush(child)
 
-    @pytest.mark.skipif(
-        not SUPPORTS_BREAKPOINT_BUILTIN, reason="Requires breakpoint() builtin"
-    )
+    @pytest.mark.xfail(reason="#10042", strict=False)
     def test_pdb_not_altered(self, pytester: Pytester) -> None:
         p1 = pytester.makepyfile(
             """
@@ -1128,7 +1215,7 @@ def test_func_kw(myparam, request, func="func_kw"):
 
 
 def test_trace_after_runpytest(pytester: Pytester) -> None:
-    """Test that debugging's pytest_configure is re-entrant."""
+    """Test that debugging's pytest_configure is reentrant."""
     p1 = pytester.makepyfile(
         """
         from _pytest.debugging import pytestPDB
@@ -1159,7 +1246,7 @@ def test_inner():
 
 
 def test_quit_with_swallowed_SystemExit(pytester: Pytester) -> None:
-    """Test that debugging's pytest_configure is re-entrant."""
+    """Test that debugging's pytest_configure is reentrant."""
     p1 = pytester.makepyfile(
         """
         def call_pdb_set_trace():
@@ -1187,10 +1274,11 @@ def test_2():
 
 
 @pytest.mark.parametrize("fixture", ("capfd", "capsys"))
+@pytest.mark.xfail(reason="#10042", strict=False)
 def test_pdb_suspends_fixture_capturing(pytester: Pytester, fixture: str) -> None:
     """Using "-s" with pytest should suspend/resume fixture capturing."""
     p1 = pytester.makepyfile(
-        """
+        f"""
         def test_inner({fixture}):
             import sys
 
@@ -1205,9 +1293,7 @@ def test_inner({fixture}):
             out, err = {fixture}.readouterr()
             assert out =="out_inner_before\\nout_inner_after\\n"
             assert err =="err_inner_before\\nerr_inner_after\\n"
-        """.format(
-            fixture=fixture
-        )
+        """
     )
 
     child = pytester.spawn_pytest(str(p1) + " -s")
@@ -1215,8 +1301,7 @@ def test_inner({fixture}):
     child.expect("Pdb")
     before = child.before.decode("utf8")
     assert (
-        "> PDB set_trace (IO-capturing turned off for fixture %s) >" % (fixture)
-        in before
+        f"> PDB set_trace (IO-capturing turned off for fixture {fixture}) >" in before
     )
 
     # Test that capturing is really suspended.
@@ -1232,7 +1317,7 @@ def test_inner({fixture}):
     TestPDB.flush(child)
     assert child.exitstatus == 0
     assert "= 1 passed in" in rest
-    assert "> PDB continue (IO-capturing resumed for fixture %s) >" % (fixture) in rest
+    assert f"> PDB continue (IO-capturing resumed for fixture {fixture}) >" in rest
 
 
 def test_pdbcls_via_local_module(pytester: Pytester) -> None:
@@ -1251,6 +1336,16 @@ def set_trace(self, *args):
 
                 def runcall(self, *args, **kwds):
                     print("runcall_called", args, kwds)
+
+                # Methods which we copy the docstring over.
+                def do_debug(self, *args):
+                    pass
+
+                def do_continue(self, *args):
+                    pass
+
+                def do_quit(self, *args):
+                    pass
         """,
     )
     result = pytester.runpytest(
@@ -1280,7 +1375,6 @@ def runcall(self, *args, **kwds):
 
 def test_raises_bdbquit_with_eoferror(pytester: Pytester) -> None:
     """It is not guaranteed that DontReadFromInput's read is called."""
-
     p1 = pytester.makepyfile(
         """
         def input_without_read(*args, **kwargs):
@@ -1318,6 +1412,16 @@ def __init__(self, *args, **kwargs):
 
             def set_trace(self, *args):
                 print("set_trace_called", args)
+
+            # Methods which we copy the docstring over.
+            def do_debug(self, *args):
+                pass
+
+            def do_continue(self, *args):
+                pass
+
+            def do_quit(self, *args):
+                pass
         """,
     )
     result = pytester.runpytest(str(p1), "--pdbcls=mypdb:MyPdb", syspathinsert=True)
diff --git a/testing/test_doctest.py b/testing/test_doctest.py
index ca215e070fa..1a852ebc82a 100644
--- a/testing/test_doctest.py
+++ b/testing/test_doctest.py
@@ -1,10 +1,12 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Callable
 import inspect
-import textwrap
 from pathlib import Path
-from typing import Callable
-from typing import Optional
+import sys
+import textwrap
 
-import pytest
 from _pytest.doctest import _get_checker
 from _pytest.doctest import _is_main_py
 from _pytest.doctest import _is_mocked
@@ -14,6 +16,7 @@
 from _pytest.doctest import DoctestModule
 from _pytest.doctest import DoctestTextfile
 from _pytest.pytester import Pytester
+import pytest
 
 
 class TestDoctests:
@@ -80,7 +83,7 @@ def test_collect_module_two_doctest_no_modulelevel(
             '# Empty'
             def my_func():
                 ">>> magic = 42 "
-            def unuseful():
+            def useless():
                 '''
                 # This is a function
                 # >>> # it doesn't have any doctest
@@ -112,6 +115,32 @@ def test_simple_doctestfile(self, pytester: Pytester):
         reprec = pytester.inline_run(p)
         reprec.assertoutcome(failed=1)
 
+    def test_importmode(self, pytester: Pytester):
+        pytester.makepyfile(
+            **{
+                "src/namespacepkg/innerpkg/__init__.py": "",
+                "src/namespacepkg/innerpkg/a.py": """
+                  def some_func():
+                    return 42
+                """,
+                "src/namespacepkg/innerpkg/b.py": """
+                  from namespacepkg.innerpkg.a import some_func
+                  def my_func():
+                    '''
+                    >>> my_func()
+                    42
+                    '''
+                    return some_func()
+                """,
+            }
+        )
+        # For 'namespacepkg' to be considered a namespace package, its containing directory
+        # needs to be reachable from sys.path:
+        # https://packaging.python.org/en/latest/guides/packaging-namespace-packages
+        pytester.syspathinsert(pytester.path / "src")
+        reprec = pytester.inline_run("--doctest-modules", "--import-mode=importlib")
+        reprec.assertoutcome(passed=1)
+
     def test_new_pattern(self, pytester: Pytester):
         p = pytester.maketxtfile(
             xdoc="""
@@ -159,19 +188,15 @@ def test_multiple_patterns(self, pytester: Pytester):
     def test_encoding(self, pytester, test_string, encoding):
         """Test support for doctest_encoding ini option."""
         pytester.makeini(
-            """
+            f"""
             [pytest]
-            doctest_encoding={}
-        """.format(
-                encoding
-            )
-        )
-        doctest = """
-            >>> "{}"
-            {}
-        """.format(
-            test_string, repr(test_string)
+            doctest_encoding={encoding}
+        """
         )
+        doctest = f"""
+            >>> "{test_string}"
+            {test_string!r}
+        """
         fn = pytester.path / "test_encoding.txt"
         fn.write_text(doctest, encoding=encoding)
 
@@ -200,6 +225,7 @@ def test_doctest_unexpected_exception(self, pytester: Pytester):
                 "Traceback (most recent call last):",
                 '  File "*/doctest.py", line *, in __run',
                 "    *",
+                *((" *^^^^*", " *", " *") if sys.version_info >= (3, 13) else ()),
                 '  File "<doctest test_doctest_unexpected_exception.txt[1]>", line 1, in <module>',
                 "ZeroDivisionError: division by zero",
                 "*/test_doctest_unexpected_exception.txt:2: UnexpectedException",
@@ -329,7 +355,8 @@ def test(self):
                         >>> 1/0
                         '''
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         result = pytester.runpytest("--doctest-modules")
         result.stdout.fnmatch_lines(
@@ -355,7 +382,7 @@ def some_property(self):
                 "*= FAILURES =*",
                 "*_ [[]doctest[]] test_doctest_linedata_on_property.Sample.some_property _*",
                 "004 ",
-                "005         >>> Sample().some_property",
+                "005 *>>> Sample().some_property",
                 "Expected:",
                 "    'another thing'",
                 "Got:",
@@ -366,7 +393,7 @@ def some_property(self):
             ]
         )
 
-    def test_doctest_no_linedata_on_overriden_property(self, pytester: Pytester):
+    def test_doctest_no_linedata_on_overridden_property(self, pytester: Pytester):
         pytester.makepyfile(
             """
             class Sample(object):
@@ -384,7 +411,7 @@ def some_property(self):
         result.stdout.fnmatch_lines(
             [
                 "*= FAILURES =*",
-                "*_ [[]doctest[]] test_doctest_no_linedata_on_overriden_property.Sample.some_property _*",
+                "*_ [[]doctest[]] test_doctest_no_linedata_on_overridden_property.Sample.some_property _*",
                 "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example",
                 "[?][?][?] >>> Sample().some_property",
                 "Expected:",
@@ -392,7 +419,7 @@ def some_property(self):
                 "Got:",
                 "    'something'",
                 "",
-                "*/test_doctest_no_linedata_on_overriden_property.py:None: DocTestFailure",
+                "*/test_doctest_no_linedata_on_overridden_property.py:None: DocTestFailure",
                 "*= 1 failed in *",
             ]
         )
@@ -420,7 +447,8 @@ def test_doctest_unex_importerror_with_module(self, pytester: Pytester):
                 """\
                 import asdalsdkjaslkdjasd
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         pytester.maketxtfile(
             """
@@ -452,6 +480,24 @@ def test_doctestmodule(self, pytester: Pytester):
         reprec = pytester.inline_run(p, "--doctest-modules")
         reprec.assertoutcome(failed=1)
 
+    def test_doctest_cached_property(self, pytester: Pytester):
+        p = pytester.makepyfile(
+            """
+            import functools
+
+            class Foo:
+                @functools.cached_property
+                def foo(self):
+                    '''
+                    >>> assert False, "Tacos!"
+                    '''
+                    ...
+        """
+        )
+        result = pytester.runpytest(p, "--doctest-modules")
+        result.assert_outcomes(failed=1)
+        assert "Tacos!" in result.stdout.str()
+
     def test_doctestmodule_external_and_issue116(self, pytester: Pytester):
         p = pytester.mkpydir("hello")
         p.joinpath("__init__.py").write_text(
@@ -464,7 +510,8 @@ def somefunc():
                         2
                     '''
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         result = pytester.runpytest(p, "--doctest-modules")
         result.stdout.fnmatch_lines(
@@ -564,7 +611,7 @@ def my_func():
                 >>> magic - 42
                 0
                 '''
-            def unuseful():
+            def useless():
                 pass
             def another():
                 '''
@@ -683,7 +730,7 @@ def foo():
                 >>> name = 'с' # not letter 'c' but instead Cyrillic 's'.
                 'anything'
                 """
-            '''
+            '''  # noqa: RUF001
         )
         result = pytester.runpytest("--doctest-modules")
         result.stdout.fnmatch_lines(["Got nothing", "* 1 failed in*"])
@@ -801,12 +848,13 @@ def test_valid_setup_py(self, pytester: Pytester):
         """
         p = pytester.makepyfile(
             setup="""
-            from setuptools import setup, find_packages
-            setup(name='sample',
-                  version='0.0',
-                  description='description',
-                  packages=find_packages()
-            )
+            if __name__ == '__main__':
+                from setuptools import setup, find_packages
+                setup(name='sample',
+                      version='0.0',
+                      description='description',
+                      packages=find_packages()
+                )
         """
         )
         result = pytester.runpytest(p, "--doctest-modules")
@@ -831,6 +879,25 @@ def test_foo():
         result = pytester.runpytest(p, "--doctest-modules")
         result.stdout.fnmatch_lines(["*collected 1 item*"])
 
+    def test_setup_module(self, pytester: Pytester) -> None:
+        """Regression test for #12011 - setup_module not executed when running
+        with `--doctest-modules`."""
+        pytester.makepyfile(
+            """
+            CONSTANT = 0
+
+            def setup_module():
+                global CONSTANT
+                CONSTANT = 1
+
+            def test():
+                assert CONSTANT == 1
+            """
+        )
+        result = pytester.runpytest("--doctest-modules")
+        assert result.ret == 0
+        result.assert_outcomes(passed=1)
+
 
 class TestLiterals:
     @pytest.mark.parametrize("config_mode", ["ini", "comment"])
@@ -851,23 +918,19 @@ def test_allow_unicode(self, pytester, config_mode):
             comment = "#doctest: +ALLOW_UNICODE"
 
         pytester.maketxtfile(
-            test_doc="""
+            test_doc=f"""
             >>> b'12'.decode('ascii') {comment}
             '12'
-        """.format(
-                comment=comment
-            )
+        """
         )
         pytester.makepyfile(
-            foo="""
+            foo=f"""
             def foo():
               '''
               >>> b'12'.decode('ascii') {comment}
               '12'
               '''
-        """.format(
-                comment=comment
-            )
+        """
         )
         reprec = pytester.inline_run("--doctest-modules")
         reprec.assertoutcome(passed=2)
@@ -890,23 +953,19 @@ def test_allow_bytes(self, pytester, config_mode):
             comment = "#doctest: +ALLOW_BYTES"
 
         pytester.maketxtfile(
-            test_doc="""
+            test_doc=f"""
             >>> b'foo'  {comment}
             'foo'
-        """.format(
-                comment=comment
-            )
+        """
         )
         pytester.makepyfile(
-            foo="""
+            foo=f"""
             def foo():
               '''
               >>> b'foo'  {comment}
               'foo'
               '''
-        """.format(
-                comment=comment
-            )
+        """
         )
         reprec = pytester.inline_run("--doctest-modules")
         reprec.assertoutcome(passed=2)
@@ -983,7 +1042,7 @@ def test_number_precision(self, pytester, config_mode):
             comment = "#doctest: +NUMBER"
 
         pytester.maketxtfile(
-            test_doc="""
+            test_doc=f"""
 
             Scalars:
 
@@ -1035,9 +1094,7 @@ def test_number_precision(self, pytester, config_mode):
             >>> 'abc' {comment}
             'abc'
             >>> None {comment}
-            """.format(
-                comment=comment
-            )
+            """
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=1)
@@ -1065,12 +1122,10 @@ def test_number_precision(self, pytester, config_mode):
     )
     def test_number_non_matches(self, pytester, expression, output):
         pytester.maketxtfile(
-            test_doc="""
+            test_doc=f"""
             >>> {expression} #doctest: +NUMBER
             {output}
-            """.format(
-                expression=expression, output=output
-            )
+            """
         )
         reprec = pytester.inline_run()
         reprec.assertoutcome(passed=0, failed=1)
@@ -1102,7 +1157,7 @@ def makeit(doctest):
                 pytester.maketxtfile(doctest)
             else:
                 assert mode == "module"
-                pytester.makepyfile('"""\n%s"""' % doctest)
+                pytester.makepyfile(f'"""\n{doctest}"""')
 
         return makeit
 
@@ -1207,7 +1262,6 @@ def my_config_context():
 
 
 class TestDoctestAutoUseFixtures:
-
     SCOPES = ["module", "session", "class", "function"]
 
     def test_doctest_module_session_fixture(self, pytester: Pytester):
@@ -1252,15 +1306,13 @@ def test_fixture_scopes(self, pytester, scope, enable_doctest):
         See #1057 and #1100.
         """
         pytester.makeconftest(
-            """
+            f"""
             import pytest
 
             @pytest.fixture(autouse=True, scope="{scope}")
             def auto(request):
                 return 99
-        """.format(
-                scope=scope
-            )
+        """
         )
         pytester.makepyfile(
             test_1='''
@@ -1276,7 +1328,7 @@ def test_bar():
         params = ("--doctest-modules",) if enable_doctest else ()
         passes = 3 if enable_doctest else 2
         result = pytester.runpytest(*params)
-        result.stdout.fnmatch_lines(["*=== %d passed in *" % passes])
+        result.stdout.fnmatch_lines([f"*=== {passes} passed in *"])
 
     @pytest.mark.parametrize("scope", SCOPES)
     @pytest.mark.parametrize("autouse", [True, False])
@@ -1288,15 +1340,13 @@ def test_fixture_module_doctest_scopes(
         See #1057 and #1100.
         """
         pytester.makeconftest(
-            """
+            f"""
             import pytest
 
             @pytest.fixture(autouse={autouse}, scope="{scope}")
             def auto(request):
                 return 99
-        """.format(
-                scope=scope, autouse=autouse
-            )
+        """
         )
         if use_fixture_in_doctest:
             pytester.maketxtfile(
@@ -1322,7 +1372,7 @@ def test_auto_use_request_attributes(self, pytester, scope):
         behave as expected when requested for a doctest item.
         """
         pytester.makeconftest(
-            """
+            f"""
             import pytest
 
             @pytest.fixture(autouse=True, scope="{scope}")
@@ -1334,9 +1384,7 @@ def auto(request):
                 if "{scope}" == 'function':
                     assert request.function is None
                 return 99
-        """.format(
-                scope=scope
-            )
+        """
         )
         pytester.maketxtfile(
             test_doc="""
@@ -1348,9 +1396,40 @@ def auto(request):
         str(result.stdout.no_fnmatch_line("*FAILURES*"))
         result.stdout.fnmatch_lines(["*=== 1 passed in *"])
 
+    @pytest.mark.parametrize("scope", [*SCOPES, "package"])
+    def test_auto_use_defined_in_same_module(
+        self, pytester: Pytester, scope: str
+    ) -> None:
+        """Autouse fixtures defined in the same module as the doctest get picked
+        up properly.
+
+        Regression test for #11929.
+        """
+        pytester.makepyfile(
+            f"""
+            import pytest
+
+            AUTO = "the fixture did not run"
+
+            @pytest.fixture(autouse=True, scope="{scope}")
+            def auto(request):
+                global AUTO
+                AUTO = "the fixture ran"
+
+            def my_doctest():
+                '''My doctest.
+
+                >>> my_doctest()
+                'the fixture ran'
+                '''
+                return AUTO
+            """
+        )
+        result = pytester.runpytest("--doctest-modules")
+        result.assert_outcomes(passed=1)
 
-class TestDoctestNamespaceFixture:
 
+class TestDoctestNamespaceFixture:
     SCOPES = ["module", "session", "class", "function"]
 
     @pytest.mark.parametrize("scope", SCOPES)
@@ -1360,16 +1439,14 @@ def test_namespace_doctestfile(self, pytester, scope):
         simple text file doctest
         """
         pytester.makeconftest(
-            """
+            f"""
             import pytest
             import contextlib
 
             @pytest.fixture(autouse=True, scope="{scope}")
             def add_contextlib(doctest_namespace):
                 doctest_namespace['cl'] = contextlib
-        """.format(
-                scope=scope
-            )
+        """
         )
         p = pytester.maketxtfile(
             """
@@ -1387,16 +1464,14 @@ def test_namespace_pyfile(self, pytester, scope):
         simple Python file docstring doctest
         """
         pytester.makeconftest(
-            """
+            f"""
             import pytest
             import contextlib
 
             @pytest.fixture(autouse=True, scope="{scope}")
             def add_contextlib(doctest_namespace):
                 doctest_namespace['cl'] = contextlib
-        """.format(
-                scope=scope
-            )
+        """
         )
         p = pytester.makepyfile(
             """
@@ -1499,16 +1574,14 @@ def test_doctest_report_invalid(self, pytester: Pytester):
 def test_doctest_mock_objects_dont_recurse_missbehaved(mock_module, pytester: Pytester):
     pytest.importorskip(mock_module)
     pytester.makepyfile(
-        """
+        f"""
         from {mock_module} import call
         class Example(object):
             '''
             >>> 1 + 1
             2
             '''
-        """.format(
-            mock_module=mock_module
-        )
+        """
     )
     result = pytester.runpytest("--doctest-modules")
     result.stdout.fnmatch_lines(["* 1 passed *"])
@@ -1523,7 +1596,7 @@ def __getattr__(self, _):
     "stop", [None, _is_mocked, lambda f: None, lambda f: False, lambda f: True]
 )
 def test_warning_on_unwrap_of_broken_object(
-    stop: Optional[Callable[[object], object]]
+    stop: Callable[[object], object] | None,
 ) -> None:
     bad_instance = Broken()
     assert inspect.unwrap.__module__ == "inspect"
@@ -1539,7 +1612,9 @@ def test_warning_on_unwrap_of_broken_object(
 
 def test_is_setup_py_not_named_setup_py(tmp_path: Path) -> None:
     not_setup_py = tmp_path.joinpath("not_setup.py")
-    not_setup_py.write_text('from setuptools import setup; setup(name="foo")')
+    not_setup_py.write_text(
+        'from setuptools import setup; setup(name="foo")', encoding="utf-8"
+    )
     assert not _is_setup_py(not_setup_py)
 
 
@@ -1555,7 +1630,7 @@ def test_is_setup_py_different_encoding(tmp_path: Path, mod: str) -> None:
     setup_py = tmp_path.joinpath("setup.py")
     contents = (
         "# -*- coding: cp1252 -*-\n"
-        'from {} import setup; setup(name="foo", description="€")\n'.format(mod)
+        f'from {mod} import setup; setup(name="foo", description="€")\n'
     )
     setup_py.write_bytes(contents.encode("cp1252"))
     assert _is_setup_py(setup_py)
diff --git a/testing/test_entry_points.py b/testing/test_entry_points.py
index 5d003127363..543f3252b22 100644
--- a/testing/test_entry_points.py
+++ b/testing/test_entry_points.py
@@ -1,7 +1,10 @@
-from _pytest.compat import importlib_metadata
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+import importlib.metadata
 
 
 def test_pytest_entry_points_are_identical():
-    dist = importlib_metadata.distribution("pytest")
+    dist = importlib.metadata.distribution("pytest")
     entry_map = {ep.name: ep for ep in dist.entry_points}
     assert entry_map["pytest"].value == entry_map["py.test"].value
diff --git a/testing/test_error_diffs.py b/testing/test_error_diffs.py
index 1668e929ab4..741a6ca82d0 100644
--- a/testing/test_error_diffs.py
+++ b/testing/test_error_diffs.py
@@ -4,10 +4,11 @@
 See https://github.com/pytest-dev/pytest/issues/3333 for details.
 
 """
-import sys
 
-import pytest
+from __future__ import annotations
+
 from _pytest.pytester import Pytester
+import pytest
 
 
 TESTCASES = [
@@ -23,10 +24,14 @@ def test_this():
         E       assert [1, 4, 3] == [1, 2, 3]
         E         At index 1 diff: 4 != 2
         E         Full diff:
-        E         - [1, 2, 3]
+        E           [
+        E               1,
+        E         -     2,
         E         ?     ^
-        E         + [1, 4, 3]
+        E         +     4,
         E         ?     ^
+        E               3,
+        E           ]
         """,
         id="Compare lists, one item differs",
     ),
@@ -42,9 +47,11 @@ def test_this():
         E       assert [1, 2, 3] == [1, 2]
         E         Left contains one more item: 3
         E         Full diff:
-        E         - [1, 2]
-        E         + [1, 2, 3]
-        E         ?      +++
+        E           [
+        E               1,
+        E               2,
+        E         +     3,
+        E           ]
         """,
         id="Compare lists, one extra item",
     ),
@@ -61,9 +68,11 @@ def test_this():
         E         At index 1 diff: 3 != 2
         E         Right contains one more item: 3
         E         Full diff:
-        E         - [1, 2, 3]
-        E         ?     ---
-        E         + [1, 3]
+        E           [
+        E               1,
+        E         -     2,
+        E               3,
+        E           ]
         """,
         id="Compare lists, one item missing",
     ),
@@ -79,10 +88,14 @@ def test_this():
         E       assert (1, 4, 3) == (1, 2, 3)
         E         At index 1 diff: 4 != 2
         E         Full diff:
-        E         - (1, 2, 3)
+        E           (
+        E               1,
+        E         -     2,
         E         ?     ^
-        E         + (1, 4, 3)
+        E         +     4,
         E         ?     ^
+        E               3,
+        E           )
         """,
         id="Compare tuples",
     ),
@@ -101,10 +114,12 @@ def test_this():
         E         Extra items in the right set:
         E         2
         E         Full diff:
-        E         - {1, 2, 3}
-        E         ?     ^  ^
-        E         + {1, 3, 4}
-        E         ?     ^  ^
+        E           {
+        E               1,
+        E         -     2,
+        E               3,
+        E         +     4,
+        E           }
         """,
         id="Compare sets",
     ),
@@ -125,10 +140,13 @@ def test_this():
         E         Right contains 1 more item:
         E         {2: 'eggs'}
         E         Full diff:
-        E         - {1: 'spam', 2: 'eggs'}
-        E         ?             ^
-        E         + {1: 'spam', 3: 'eggs'}
-        E         ?             ^
+        E           {
+        E               1: 'spam',
+        E         -     2: 'eggs',
+        E         ?     ^
+        E         +     3: 'eggs',
+        E         ?     ^
+        E           }
         """,
         id="Compare dicts with differing keys",
     ),
@@ -147,10 +165,11 @@ def test_this():
         E         Differing items:
         E         {2: 'eggs'} != {2: 'bacon'}
         E         Full diff:
-        E         - {1: 'spam', 2: 'bacon'}
-        E         ?                 ^^^^^
-        E         + {1: 'spam', 2: 'eggs'}
-        E         ?                 ^^^^
+        E           {
+        E               1: 'spam',
+        E         -     2: 'bacon',
+        E         +     2: 'eggs',
+        E           }
         """,
         id="Compare dicts with differing values",
     ),
@@ -171,10 +190,11 @@ def test_this():
         E         Right contains 1 more item:
         E         {3: 'bacon'}
         E         Full diff:
-        E         - {1: 'spam', 3: 'bacon'}
-        E         ?             ^   ^^^^^
-        E         + {1: 'spam', 2: 'eggs'}
-        E         ?             ^   ^^^^
+        E           {
+        E               1: 'spam',
+        E         -     3: 'bacon',
+        E         +     2: 'eggs',
+        E           }
         """,
         id="Compare dicts with differing items",
     ),
@@ -210,68 +230,61 @@ def test_this():
         """,
         id='Test "not in" string',
     ),
-]
-if sys.version_info[:2] >= (3, 7):
-    TESTCASES.extend(
-        [
-            pytest.param(
-                """
-                from dataclasses import dataclass
+    pytest.param(
+        """
+        from dataclasses import dataclass
 
-                @dataclass
-                class A:
-                    a: int
-                    b: str
+        @dataclass
+        class A:
+            a: int
+            b: str
 
-                def test_this():
-                    result =   A(1, 'spam')
-                    expected = A(2, 'spam')
-                    assert result == expected
-                """,
-                """
-                >       assert result == expected
-                E       AssertionError: assert A(a=1, b='spam') == A(a=2, b='spam')
-                E         Matching attributes:
-                E         ['b']
-                E         Differing attributes:
-                E         ['a']
-                E         Drill down into differing attribute a:
-                E           a: 1 != 2
-                E           +1
-                E           -2
-                """,
-                id="Compare data classes",
-            ),
-            pytest.param(
-                """
-                import attr
+        def test_this():
+            result =   A(1, 'spam')
+            expected = A(2, 'spam')
+            assert result == expected
+        """,
+        """
+        >       assert result == expected
+        E       AssertionError: assert A(a=1, b='spam') == A(a=2, b='spam')
+        E         Matching attributes:
+        E         ['b']
+        E         Differing attributes:
+        E         ['a']
+        E         Drill down into differing attribute a:
+        E           a: 1 != 2
+        """,
+        id="Compare data classes",
+    ),
+    pytest.param(
+        """
+        import attr
 
-                @attr.s(auto_attribs=True)
-                class A:
-                    a: int
-                    b: str
+        @attr.s(auto_attribs=True)
+        class A:
+            a: int
+            b: str
 
-                def test_this():
-                    result =   A(1, 'spam')
-                    expected = A(1, 'eggs')
-                    assert result == expected
-                """,
-                """
-                >       assert result == expected
-                E       AssertionError: assert A(a=1, b='spam') == A(a=1, b='eggs')
-                E         Matching attributes:
-                E         ['a']
-                E         Differing attributes:
-                E         ['b']
-                E         Drill down into differing attribute b:
-                E           b: 'spam' != 'eggs'
-                E           - eggs
-                E           + spam
-                """,
-                id="Compare attrs classes",
-            ),
-        ]
-    )
+        def test_this():
+            result =   A(1, 'spam')
+            expected = A(1, 'eggs')
+            assert result == expected
+        """,
+        """
+        >       assert result == expected
+        E       AssertionError: assert A(a=1, b='spam') == A(a=1, b='eggs')
+        E         Matching attributes:
+        E         ['a']
+        E         Differing attributes:
+        E         ['b']
+        E         Drill down into differing attribute b:
+        E           b: 'spam' != 'eggs'
+        E           - eggs
+        E           + spam
+        """,
+        id="Compare attrs classes",
+    ),
+]
 
 
 @pytest.mark.parametrize("code, expected", TESTCASES)
diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py
index 5b7911f21f8..c416e81d2d9 100644
--- a/testing/test_faulthandler.py
+++ b/testing/test_faulthandler.py
@@ -1,8 +1,11 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import io
 import sys
 
-import pytest
 from _pytest.pytester import Pytester
+import pytest
 
 
 def test_enabled(pytester: Pytester) -> None:
@@ -100,7 +103,7 @@ def test_timeout():
     result = pytester.runpytest_subprocess(*args)
     tb_output = "most recent call first"
     if enabled:
-        result.stderr.fnmatch_lines(["*%s*" % tb_output])
+        result.stderr.fnmatch_lines([f"*{tb_output}*"])
     else:
         assert tb_output not in result.stderr.str()
     result.stdout.fnmatch_lines(["*1 passed*"])
@@ -113,6 +116,7 @@ def test_cancel_timeout_on_hook(monkeypatch, hook_name) -> None:
     to timeout before entering pdb (pytest-dev/pytest-faulthandler#12) or any
     other interactive exception (pytest-dev/pytest-faulthandler#14)."""
     import faulthandler
+
     from _pytest import faulthandler as faulthandler_plugin
 
     called = []
diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py
index 3a2917261a2..9532f1eef75 100644
--- a/testing/test_findpaths.py
+++ b/testing/test_findpaths.py
@@ -1,11 +1,16 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+import os
 from pathlib import Path
 from textwrap import dedent
 
-import pytest
 from _pytest.config import UsageError
 from _pytest.config.findpaths import get_common_ancestor
 from _pytest.config.findpaths import get_dirs_from_args
+from _pytest.config.findpaths import is_fs_root
 from _pytest.config.findpaths import load_config_dict_from_file
+import pytest
 
 
 class TestLoadConfigDictFromFile:
@@ -107,18 +112,19 @@ def test_has_ancestor(self, tmp_path: Path) -> None:
         fn2 = tmp_path / "foo" / "zaz" / "test_2.py"
         fn2.parent.mkdir(parents=True)
         fn2.touch()
-        assert get_common_ancestor([fn1, fn2]) == tmp_path / "foo"
-        assert get_common_ancestor([fn1.parent, fn2]) == tmp_path / "foo"
-        assert get_common_ancestor([fn1.parent, fn2.parent]) == tmp_path / "foo"
-        assert get_common_ancestor([fn1, fn2.parent]) == tmp_path / "foo"
+        cwd = Path.cwd()
+        assert get_common_ancestor(cwd, [fn1, fn2]) == tmp_path / "foo"
+        assert get_common_ancestor(cwd, [fn1.parent, fn2]) == tmp_path / "foo"
+        assert get_common_ancestor(cwd, [fn1.parent, fn2.parent]) == tmp_path / "foo"
+        assert get_common_ancestor(cwd, [fn1, fn2.parent]) == tmp_path / "foo"
 
     def test_single_dir(self, tmp_path: Path) -> None:
-        assert get_common_ancestor([tmp_path]) == tmp_path
+        assert get_common_ancestor(Path.cwd(), [tmp_path]) == tmp_path
 
     def test_single_file(self, tmp_path: Path) -> None:
         fn = tmp_path / "foo.py"
         fn.touch()
-        assert get_common_ancestor([fn]) == tmp_path
+        assert get_common_ancestor(Path.cwd(), [fn]) == tmp_path
 
 
 def test_get_dirs_from_args(tmp_path):
@@ -133,3 +139,18 @@ def test_get_dirs_from_args(tmp_path):
     assert get_dirs_from_args(
         [str(fn), str(tmp_path / "does_not_exist"), str(d), option, xdist_rsync_option]
     ) == [fn.parent, d]
+
+
+@pytest.mark.parametrize(
+    "path, expected",
+    [
+        pytest.param(
+            f"e:{os.sep}", True, marks=pytest.mark.skipif("sys.platform != 'win32'")
+        ),
+        (f"{os.sep}", True),
+        (f"e:{os.sep}projects", False),
+        (f"{os.sep}projects", False),
+    ],
+)
+def test_is_fs_root(path: Path, expected: bool) -> None:
+    assert is_fs_root(Path(path)) is expected
diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py
index 44c2c9295bf..dc7e709b65d 100644
--- a/testing/test_helpconfig.py
+++ b/testing/test_helpconfig.py
@@ -1,19 +1,24 @@
-import pytest
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from _pytest.config import ExitCode
 from _pytest.pytester import Pytester
+import pytest
 
 
 def test_version_verbose(pytester: Pytester, pytestconfig, monkeypatch) -> None:
     monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
+    monkeypatch.delenv("PYTEST_PLUGINS", raising=False)
     result = pytester.runpytest("--version", "--version")
     assert result.ret == 0
     result.stdout.fnmatch_lines([f"*pytest*{pytest.__version__}*imported from*"])
     if pytestconfig.pluginmanager.list_plugin_distinfo():
-        result.stdout.fnmatch_lines(["*setuptools registered plugins:", "*at*"])
+        result.stdout.fnmatch_lines(["*registered third-party plugins:", "*at*"])
 
 
 def test_version_less_verbose(pytester: Pytester, pytestconfig, monkeypatch) -> None:
     monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
+    monkeypatch.delenv("PYTEST_PLUGINS", raising=False)
     result = pytester.runpytest("--version")
     assert result.ret == 0
     result.stdout.fnmatch_lines([f"pytest {pytest.__version__}"])
@@ -30,11 +35,11 @@ def test_help(pytester: Pytester) -> None:
     assert result.ret == 0
     result.stdout.fnmatch_lines(
         """
-          -m MARKEXPR           only run tests matching given mark expression.
-                                For example: -m 'mark1 and not mark2'.
-        reporting:
+          -m MARKEXPR           Only run tests matching given mark expression. For
+                                example: -m 'mark1 and not mark2'.
+        Reporting:
           --durations=N *
-          -V, --version         display pytest version and information about plugins.
+          -V, --version         Display pytest version and information about plugins.
                                 When given twice, also display information about
                                 plugins.
         *setup.cfg*
@@ -71,9 +76,9 @@ def pytest_addoption(parser):
     assert result.ret == 0
     lines = [
         "  required_plugins (args):",
-        "                        plugins that must be present for pytest to run*",
+        "                        Plugins that must be present for pytest to run*",
         "  test_ini (bool):*",
-        "environment variables:",
+        "Environment variables:",
     ]
     result.stdout.fnmatch_lines(lines, consecutive=True)
 
diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py
index 02531e81435..4145f34ab14 100644
--- a/testing/test_junitxml.py
+++ b/testing/test_junitxml.py
@@ -1,18 +1,17 @@
-import os
-import platform
+from __future__ import annotations
+
 from datetime import datetime
+from datetime import timezone
+import os
 from pathlib import Path
+import platform
+from typing import Any
 from typing import cast
-from typing import List
-from typing import Optional
-from typing import Tuple
 from typing import TYPE_CHECKING
-from typing import Union
 from xml.dom import minidom
 
 import xmlschema
 
-import pytest
 from _pytest.config import Config
 from _pytest.junitxml import bin_xml_escape
 from _pytest.junitxml import LogXML
@@ -22,13 +21,15 @@
 from _pytest.reports import BaseReport
 from _pytest.reports import TestReport
 from _pytest.stash import Stash
+import _pytest.timing
+import pytest
 
 
 @pytest.fixture(scope="session")
 def schema() -> xmlschema.XMLSchema:
     """Return an xmlschema.XMLSchema object for the junit-10.xsd file."""
     fn = Path(__file__).parent / "example_scripts/junit-10.xsd"
-    with fn.open() as f:
+    with fn.open(encoding="utf-8") as f:
         return xmlschema.XMLSchema(f)
 
 
@@ -38,17 +39,17 @@ def __init__(self, pytester: Pytester, schema: xmlschema.XMLSchema) -> None:
         self.schema = schema
 
     def __call__(
-        self, *args: Union[str, "os.PathLike[str]"], family: Optional[str] = "xunit1"
-    ) -> Tuple[RunResult, "DomNode"]:
+        self, *args: str | os.PathLike[str], family: str | None = "xunit1"
+    ) -> tuple[RunResult, DomDocument]:
         if family:
-            args = ("-o", "junit_family=" + family) + args
+            args = ("-o", "junit_family=" + family, *args)
         xml_path = self.pytester.path.joinpath("junit.xml")
-        result = self.pytester.runpytest("--junitxml=%s" % xml_path, *args)
+        result = self.pytester.runpytest(f"--junitxml={xml_path}", *args)
         if family == "xunit2":
-            with xml_path.open() as f:
+            with xml_path.open(encoding="utf-8") as f:
                 self.schema.validate(f)
         xmldoc = minidom.parse(str(xml_path))
-        return result, DomNode(xmldoc)
+        return result, DomDocument(xmldoc)
 
 
 @pytest.fixture
@@ -62,78 +63,129 @@ def run_and_parse(pytester: Pytester, schema: xmlschema.XMLSchema) -> RunAndPars
     return RunAndParse(pytester, schema)
 
 
-def assert_attr(node, **kwargs):
+def assert_attr(node: minidom.Element, **kwargs: object) -> None:
     __tracebackhide__ = True
 
-    def nodeval(node, name):
+    def nodeval(node: minidom.Element, name: str) -> str | None:
         anode = node.getAttributeNode(name)
-        if anode is not None:
-            return anode.value
+        return anode.value if anode is not None else None
 
     expected = {name: str(value) for name, value in kwargs.items()}
     on_node = {name: nodeval(node, name) for name in expected}
     assert on_node == expected
 
 
-class DomNode:
-    def __init__(self, dom):
-        self.__node = dom
+class DomDocument:
+    def __init__(self, dom: minidom.Document):
+        self._node = dom
 
-    def __repr__(self):
-        return self.__node.toxml()
+    _node: minidom.Document | minidom.Element
 
-    def find_first_by_tag(self, tag):
+    def find_first_by_tag(self, tag: str) -> DomNode | None:
         return self.find_nth_by_tag(tag, 0)
 
-    def _by_tag(self, tag):
-        return self.__node.getElementsByTagName(tag)
+    def get_first_by_tag(self, tag: str) -> DomNode:
+        maybe = self.find_first_by_tag(tag)
+        if maybe is None:
+            raise LookupError(tag)
+        else:
+            return maybe
+
+    def find_nth_by_tag(self, tag: str, n: int) -> DomNode | None:
+        items = self._node.getElementsByTagName(tag)
+        try:
+            nth = items[n]
+        except IndexError:
+            return None
+        else:
+            return DomNode(nth)
+
+    def find_by_tag(self, tag: str) -> list[DomNode]:
+        return [DomNode(x) for x in self._node.getElementsByTagName(tag)]
 
     @property
-    def children(self):
-        return [type(self)(x) for x in self.__node.childNodes]
+    def children(self) -> list[DomNode]:
+        return [DomNode(x) for x in self._node.childNodes]
 
     @property
-    def get_unique_child(self):
+    def get_unique_child(self) -> DomNode:
         children = self.children
         assert len(children) == 1
         return children[0]
 
-    def find_nth_by_tag(self, tag, n):
-        items = self._by_tag(tag)
-        try:
-            nth = items[n]
-        except IndexError:
-            pass
-        else:
-            return type(self)(nth)
+    def toxml(self) -> str:
+        return self._node.toxml()
+
 
-    def find_by_tag(self, tag):
-        t = type(self)
-        return [t(x) for x in self.__node.getElementsByTagName(tag)]
+class DomNode(DomDocument):
+    _node: minidom.Element
 
-    def __getitem__(self, key):
-        node = self.__node.getAttributeNode(key)
+    def __init__(self, dom: minidom.Element):
+        self._node = dom
+
+    def __repr__(self) -> str:
+        return self.toxml()
+
+    def __getitem__(self, key: str) -> str:
+        node = self._node.getAttributeNode(key)
         if node is not None:
-            return node.value
+            return cast(str, node.value)
+        else:
+            raise KeyError(key)
 
-    def assert_attr(self, **kwargs):
+    def assert_attr(self, **kwargs: object) -> None:
         __tracebackhide__ = True
-        return assert_attr(self.__node, **kwargs)
-
-    def toxml(self):
-        return self.__node.toxml()
+        return assert_attr(self._node, **kwargs)
 
     @property
-    def text(self):
-        return self.__node.childNodes[0].wholeText
+    def text(self) -> str:
+        return cast(str, self._node.childNodes[0].wholeText)
 
     @property
-    def tag(self):
-        return self.__node.tagName
+    def tag(self) -> str:
+        return self._node.tagName
 
-    @property
-    def next_sibling(self):
-        return type(self)(self.__node.nextSibling)
+
+class TestJunitHelpers:
+    """minimal test to increase coverage for methods that are used in debugging"""
+
+    @pytest.fixture
+    def document(self) -> DomDocument:
+        doc = minidom.parseString("""
+        <root>
+          <item name="a"></item>
+          <item name="b"></item>
+        </root>
+""")
+        return DomDocument(doc)
+
+    def test_uc_root(self, document: DomDocument) -> None:
+        assert document.get_unique_child.tag == "root"
+
+    def test_node_assert_attr(self, document: DomDocument) -> None:
+        item = document.get_first_by_tag("item")
+
+        item.assert_attr(name="a")
+
+        with pytest.raises(AssertionError):
+            item.assert_attr(missing="foo")
+
+    def test_node_getitem(self, document: DomDocument) -> None:
+        item = document.get_first_by_tag("item")
+        assert item["name"] == "a"
+
+        with pytest.raises(KeyError, match="missing"):
+            item["missing"]
+
+    def test_node_get_first_lookup(self, document: DomDocument) -> None:
+        with pytest.raises(LookupError, match="missing"):
+            document.get_first_by_tag("missing")
+
+    def test_node_repr(self, document: DomDocument) -> None:
+        item = document.get_first_by_tag("item")
+
+        assert repr(item) == item.toxml()
+        assert item.toxml() == '<item name="a"/>'
 
 
 parametrize_families = pytest.mark.parametrize("xunit_family", ["xunit1", "xunit2"])
@@ -163,7 +215,7 @@ def test_xpass():
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(name="pytest", errors=0, failures=1, skipped=2, tests=5)
 
     @parametrize_families
@@ -192,7 +244,7 @@ def test_xpass():
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(name="pytest", errors=1, failures=2, skipped=1, tests=5)
 
     @parametrize_families
@@ -206,7 +258,7 @@ def test_pass():
         """
         )
         result, dom = run_and_parse(family=xunit_family)
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(hostname=platform.node())
 
     @parametrize_families
@@ -219,14 +271,17 @@ def test_pass():
                 pass
         """
         )
-        start_time = datetime.now()
+        start_time = datetime.now(timezone.utc)
         result, dom = run_and_parse(family=xunit_family)
-        node = dom.find_first_by_tag("testsuite")
-        timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f")
-        assert start_time <= timestamp < datetime.now()
+        node = dom.get_first_by_tag("testsuite")
+        timestamp = datetime.fromisoformat(node["timestamp"])
+        assert start_time <= timestamp < datetime.now(timezone.utc)
 
     def test_timing_function(
-        self, pytester: Pytester, run_and_parse: RunAndParse, mock_timing
+        self,
+        pytester: Pytester,
+        run_and_parse: RunAndParse,
+        mock_timing: _pytest.timing.MockTiming,
     ) -> None:
         pytester.makepyfile(
             """
@@ -240,9 +295,10 @@ def test_sleep():
         """
         )
         result, dom = run_and_parse()
-        node = dom.find_first_by_tag("testsuite")
-        tnode = node.find_first_by_tag("testcase")
+        node = dom.get_first_by_tag("testsuite")
+        tnode = node.get_first_by_tag("testcase")
         val = tnode["time"]
+        assert val is not None
         assert float(val) == 7.0
 
     @pytest.mark.parametrize("duration_report", ["call", "total"])
@@ -253,11 +309,10 @@ def test_junit_duration_report(
         duration_report: str,
         run_and_parse: RunAndParse,
     ) -> None:
-
         # mock LogXML.node_reporter so it always sets a known duration to each test report object
         original_node_reporter = LogXML.node_reporter
 
-        def node_reporter_wrapper(s, report):
+        def node_reporter_wrapper(s: Any, report: TestReport) -> Any:
             report.duration = 1.0
             reporter = original_node_reporter(s, report)
             return reporter
@@ -271,8 +326,8 @@ def test_foo():
         """
         )
         result, dom = run_and_parse("-o", f"junit_duration_report={duration_report}")
-        node = dom.find_first_by_tag("testsuite")
-        tnode = node.find_first_by_tag("testcase")
+        node = dom.get_first_by_tag("testsuite")
+        tnode = node.get_first_by_tag("testcase")
         val = float(tnode["time"])
         if duration_report == "total":
             assert val == 3.0
@@ -297,11 +352,11 @@ def test_function(arg):
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(errors=1, tests=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="test_setup_error", name="test_function")
-        fnode = tnode.find_first_by_tag("error")
+        fnode = tnode.get_first_by_tag("error")
         fnode.assert_attr(message='failed on setup with "ValueError: Error reason"')
         assert "ValueError" in fnode.toxml()
 
@@ -323,10 +378,10 @@ def test_function(arg):
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
-        tnode = node.find_first_by_tag("testcase")
+        node = dom.get_first_by_tag("testsuite")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="test_teardown_error", name="test_function")
-        fnode = tnode.find_first_by_tag("error")
+        fnode = tnode.get_first_by_tag("error")
         fnode.assert_attr(message='failed on teardown with "ValueError: Error reason"')
         assert "ValueError" in fnode.toxml()
 
@@ -348,15 +403,15 @@ def test_function(arg):
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(errors=1, failures=1, tests=1)
         first, second = dom.find_by_tag("testcase")
         assert first
         assert second
         assert first != second
-        fnode = first.find_first_by_tag("failure")
+        fnode = first.get_first_by_tag("failure")
         fnode.assert_attr(message="Exception: Call Exception")
-        snode = second.find_first_by_tag("error")
+        snode = second.get_first_by_tag("error")
         snode.assert_attr(
             message='failed on teardown with "Exception: Teardown Exception"'
         )
@@ -374,11 +429,11 @@ def test_skip():
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret == 0
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(skipped=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="test_skip_contains_name_reason", name="test_skip")
-        snode = tnode.find_first_by_tag("skipped")
+        snode = tnode.get_first_by_tag("skipped")
         snode.assert_attr(type="pytest.skip", message="hello23")
 
     @parametrize_families
@@ -395,13 +450,13 @@ def test_skip():
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret == 0
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(skipped=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(
             classname="test_mark_skip_contains_name_reason", name="test_skip"
         )
-        snode = tnode.find_first_by_tag("skipped")
+        snode = tnode.get_first_by_tag("skipped")
         snode.assert_attr(type="pytest.skip", message="hello24")
 
     @parametrize_families
@@ -419,13 +474,13 @@ def test_skip():
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret == 0
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(skipped=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(
             classname="test_mark_skipif_contains_name_reason", name="test_skip"
         )
-        snode = tnode.find_first_by_tag("skipped")
+        snode = tnode.get_first_by_tag("skipped")
         snode.assert_attr(type="pytest.skip", message="hello25")
 
     @parametrize_families
@@ -442,7 +497,7 @@ def test_skip():
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret == 0
-        node_xml = dom.find_first_by_tag("testsuite").toxml()
+        node_xml = dom.get_first_by_tag("testsuite").toxml()
         assert "bar!" not in node_xml
 
     @parametrize_families
@@ -458,9 +513,9 @@ def test_method(self):
         )
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(failures=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(
             classname="test_classname_instance.TestClass", name="test_method"
         )
@@ -470,12 +525,12 @@ def test_classname_nested_dir(
         self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str
     ) -> None:
         p = pytester.mkdir("sub").joinpath("test_hello.py")
-        p.write_text("def test_func(): 0/0")
+        p.write_text("def test_func(): 0/0", encoding="utf-8")
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(failures=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="sub.test_hello", name="test_func")
 
     @parametrize_families
@@ -486,11 +541,11 @@ def test_internal_error(
         pytester.makepyfile("def test_function(): pass")
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(errors=1, tests=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="pytest", name="internal")
-        fnode = tnode.find_first_by_tag("error")
+        fnode = tnode.get_first_by_tag("error")
         fnode.assert_attr(message="internal error")
         assert "Division" in fnode.toxml()
 
@@ -501,9 +556,9 @@ def test_internal_error(
     def test_failure_function(
         self,
         pytester: Pytester,
-        junit_logging,
+        junit_logging: str,
         run_and_parse: RunAndParse,
-        xunit_family,
+        xunit_family: str,
     ) -> None:
         pytester.makepyfile(
             """
@@ -520,50 +575,50 @@ def test_fail():
         )
 
         result, dom = run_and_parse(
-            "-o", "junit_logging=%s" % junit_logging, family=xunit_family
+            "-o", f"junit_logging={junit_logging}", family=xunit_family
         )
         assert result.ret, "Expected ret > 0"
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(failures=1, tests=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="test_failure_function", name="test_fail")
-        fnode = tnode.find_first_by_tag("failure")
+        fnode = tnode.get_first_by_tag("failure")
         fnode.assert_attr(message="ValueError: 42")
         assert "ValueError" in fnode.toxml(), "ValueError not included"
 
         if junit_logging in ["log", "all"]:
-            logdata = tnode.find_first_by_tag("system-out")
+            logdata = tnode.get_first_by_tag("system-out")
             log_xml = logdata.toxml()
             assert logdata.tag == "system-out", "Expected tag: system-out"
             assert "info msg" not in log_xml, "Unexpected INFO message"
             assert "warning msg" in log_xml, "Missing WARN message"
         if junit_logging in ["system-out", "out-err", "all"]:
-            systemout = tnode.find_first_by_tag("system-out")
+            systemout = tnode.get_first_by_tag("system-out")
             systemout_xml = systemout.toxml()
             assert systemout.tag == "system-out", "Expected tag: system-out"
             assert "info msg" not in systemout_xml, "INFO message found in system-out"
-            assert (
-                "hello-stdout" in systemout_xml
-            ), "Missing 'hello-stdout' in system-out"
+            assert "hello-stdout" in systemout_xml, (
+                "Missing 'hello-stdout' in system-out"
+            )
         if junit_logging in ["system-err", "out-err", "all"]:
-            systemerr = tnode.find_first_by_tag("system-err")
+            systemerr = tnode.get_first_by_tag("system-err")
             systemerr_xml = systemerr.toxml()
             assert systemerr.tag == "system-err", "Expected tag: system-err"
             assert "info msg" not in systemerr_xml, "INFO message found in system-err"
-            assert (
-                "hello-stderr" in systemerr_xml
-            ), "Missing 'hello-stderr' in system-err"
-            assert (
-                "warning msg" not in systemerr_xml
-            ), "WARN message found in system-err"
+            assert "hello-stderr" in systemerr_xml, (
+                "Missing 'hello-stderr' in system-err"
+            )
+            assert "warning msg" not in systemerr_xml, (
+                "WARN message found in system-err"
+            )
         if junit_logging == "no":
             assert not tnode.find_by_tag("log"), "Found unexpected content: log"
-            assert not tnode.find_by_tag(
-                "system-out"
-            ), "Found unexpected content: system-out"
-            assert not tnode.find_by_tag(
-                "system-err"
-            ), "Found unexpected content: system-err"
+            assert not tnode.find_by_tag("system-out"), (
+                "Found unexpected content: system-out"
+            )
+            assert not tnode.find_by_tag("system-err"), (
+                "Found unexpected content: system-err"
+            )
 
     @parametrize_families
     def test_failure_verbose_message(
@@ -577,9 +632,9 @@ def test_fail():
         """
         )
         result, dom = run_and_parse(family=xunit_family)
-        node = dom.find_first_by_tag("testsuite")
-        tnode = node.find_first_by_tag("testcase")
-        fnode = tnode.find_first_by_tag("failure")
+        node = dom.get_first_by_tag("testsuite")
+        tnode = node.get_first_by_tag("testcase")
+        fnode = tnode.get_first_by_tag("failure")
         fnode.assert_attr(message="AssertionError: An error\nassert 0")
 
     @parametrize_families
@@ -599,18 +654,17 @@ def test_func(arg1):
             "-o", "junit_logging=system-out", family=xunit_family
         )
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(failures=3, tests=3)
-
-        for index, char in enumerate("<&'"):
-
-            tnode = node.find_nth_by_tag("testcase", index)
+        tnodes = node.find_by_tag("testcase")
+        assert len(tnodes) == 3
+        for tnode, char in zip(tnodes, "<&'"):
             tnode.assert_attr(
-                classname="test_failure_escape", name="test_func[%s]" % char
+                classname="test_failure_escape", name=f"test_func[{char}]"
             )
-            sysout = tnode.find_first_by_tag("system-out")
+            sysout = tnode.get_first_by_tag("system-out")
             text = sysout.text
-            assert "%s\n" % char in text
+            assert f"{char}\n" in text
 
     @parametrize_families
     def test_junit_prefixing(
@@ -627,11 +681,11 @@ def test_hello(self):
         )
         result, dom = run_and_parse("--junitprefix=xyz", family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(failures=1, tests=2)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="xyz.test_junit_prefixing", name="test_func")
-        tnode = node.find_nth_by_tag("testcase", 1)
+        tnode = node.find_by_tag("testcase")[1]
         tnode.assert_attr(
             classname="xyz.test_junit_prefixing.TestHello", name="test_hello"
         )
@@ -649,11 +703,11 @@ def test_xfail():
         )
         result, dom = run_and_parse(family=xunit_family)
         assert not result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(skipped=1, tests=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="test_xfailure_function", name="test_xfail")
-        fnode = tnode.find_first_by_tag("skipped")
+        fnode = tnode.get_first_by_tag("skipped")
         fnode.assert_attr(type="pytest.xfail", message="42")
 
     @parametrize_families
@@ -670,11 +724,11 @@ def test_xfail():
         )
         result, dom = run_and_parse(family=xunit_family)
         assert not result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(skipped=1, tests=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="test_xfailure_marker", name="test_xfail")
-        fnode = tnode.find_first_by_tag("skipped")
+        fnode = tnode.get_first_by_tag("skipped")
         fnode.assert_attr(type="pytest.xfail", message="42")
 
     @pytest.mark.parametrize(
@@ -695,18 +749,18 @@ def test_fail():
                 assert 0
         """
         )
-        result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
-        node = dom.find_first_by_tag("testsuite")
-        tnode = node.find_first_by_tag("testcase")
-        if junit_logging in ["system-err", "out-err", "all"]:
-            assert len(tnode.find_by_tag("system-err")) == 1
-        else:
-            assert len(tnode.find_by_tag("system-err")) == 0
+        result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
+        node = dom.get_first_by_tag("testsuite")
+        tnode = node.get_first_by_tag("testcase")
 
-        if junit_logging in ["log", "system-out", "out-err", "all"]:
-            assert len(tnode.find_by_tag("system-out")) == 1
-        else:
-            assert len(tnode.find_by_tag("system-out")) == 0
+        has_err_logging = junit_logging in ["system-err", "out-err", "all"]
+        expected_err_output_len = 1 if has_err_logging else 0
+        assert len(tnode.find_by_tag("system-err")) == expected_err_output_len
+
+        has_out_logigng = junit_logging in ("log", "system-out", "out-err", "all")
+        expected_out_output_len = 1 if has_out_logigng else 0
+
+        assert len(tnode.find_by_tag("system-out")) == expected_out_output_len
 
     @parametrize_families
     def test_xfailure_xpass(
@@ -722,9 +776,9 @@ def test_xpass():
         )
         result, dom = run_and_parse(family=xunit_family)
         # assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(skipped=0, tests=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="test_xfailure_xpass", name="test_xpass")
 
     @parametrize_families
@@ -741,11 +795,11 @@ def test_xpass():
         )
         result, dom = run_and_parse(family=xunit_family)
         # assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(skipped=0, tests=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(classname="test_xfailure_xpass_strict", name="test_xpass")
-        fnode = tnode.find_first_by_tag("failure")
+        fnode = tnode.get_first_by_tag("failure")
         fnode.assert_attr(message="[XPASS(strict)] This needs to fail!")
 
     @parametrize_families
@@ -755,28 +809,27 @@ def test_collect_error(
         pytester.makepyfile("syntax error")
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(errors=1, tests=1)
-        tnode = node.find_first_by_tag("testcase")
-        fnode = tnode.find_first_by_tag("error")
+        tnode = node.get_first_by_tag("testcase")
+        fnode = tnode.get_first_by_tag("error")
         fnode.assert_attr(message="collection failure")
         assert "SyntaxError" in fnode.toxml()
 
     def test_unicode(self, pytester: Pytester, run_and_parse: RunAndParse) -> None:
         value = "hx\xc4\x85\xc4\x87\n"
         pytester.makepyfile(
-            """\
+            f"""\
             # coding: latin1
             def test_hello():
-                print(%r)
+                print({value!r})
                 assert 0
             """
-            % value
         )
         result, dom = run_and_parse()
         assert result.ret == 1
-        tnode = dom.find_first_by_tag("testcase")
-        fnode = tnode.find_first_by_tag("failure")
+        tnode = dom.get_first_by_tag("testcase")
+        fnode = tnode.get_first_by_tag("failure")
         assert "hx" in fnode.toxml()
 
     def test_assertion_binchars(
@@ -806,18 +859,18 @@ def test_pass():
                 print('hello-stdout')
         """
         )
-        result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
-        node = dom.find_first_by_tag("testsuite")
-        pnode = node.find_first_by_tag("testcase")
+        result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
+        node = dom.get_first_by_tag("testsuite")
+        pnode = node.get_first_by_tag("testcase")
         if junit_logging == "no":
-            assert not node.find_by_tag(
-                "system-out"
-            ), "system-out should not be generated"
+            assert not node.find_by_tag("system-out"), (
+                "system-out should not be generated"
+            )
         if junit_logging == "system-out":
-            systemout = pnode.find_first_by_tag("system-out")
-            assert (
-                "hello-stdout" in systemout.toxml()
-            ), "'hello-stdout' should be in system-out"
+            systemout = pnode.get_first_by_tag("system-out")
+            assert "hello-stdout" in systemout.toxml(), (
+                "'hello-stdout' should be in system-out"
+            )
 
     @pytest.mark.parametrize("junit_logging", ["no", "system-err"])
     def test_pass_captures_stderr(
@@ -830,18 +883,18 @@ def test_pass():
                 sys.stderr.write('hello-stderr')
         """
         )
-        result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
-        node = dom.find_first_by_tag("testsuite")
-        pnode = node.find_first_by_tag("testcase")
+        result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
+        node = dom.get_first_by_tag("testsuite")
+        pnode = node.get_first_by_tag("testcase")
         if junit_logging == "no":
-            assert not node.find_by_tag(
-                "system-err"
-            ), "system-err should not be generated"
+            assert not node.find_by_tag("system-err"), (
+                "system-err should not be generated"
+            )
         if junit_logging == "system-err":
-            systemerr = pnode.find_first_by_tag("system-err")
-            assert (
-                "hello-stderr" in systemerr.toxml()
-            ), "'hello-stderr' should be in system-err"
+            systemerr = pnode.get_first_by_tag("system-err")
+            assert "hello-stderr" in systemerr.toxml(), (
+                "'hello-stderr' should be in system-err"
+            )
 
     @pytest.mark.parametrize("junit_logging", ["no", "system-out"])
     def test_setup_error_captures_stdout(
@@ -859,18 +912,18 @@ def test_function(arg):
                 pass
         """
         )
-        result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
-        node = dom.find_first_by_tag("testsuite")
-        pnode = node.find_first_by_tag("testcase")
+        result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
+        node = dom.get_first_by_tag("testsuite")
+        pnode = node.get_first_by_tag("testcase")
         if junit_logging == "no":
-            assert not node.find_by_tag(
-                "system-out"
-            ), "system-out should not be generated"
+            assert not node.find_by_tag("system-out"), (
+                "system-out should not be generated"
+            )
         if junit_logging == "system-out":
-            systemout = pnode.find_first_by_tag("system-out")
-            assert (
-                "hello-stdout" in systemout.toxml()
-            ), "'hello-stdout' should be in system-out"
+            systemout = pnode.get_first_by_tag("system-out")
+            assert "hello-stdout" in systemout.toxml(), (
+                "'hello-stdout' should be in system-out"
+            )
 
     @pytest.mark.parametrize("junit_logging", ["no", "system-err"])
     def test_setup_error_captures_stderr(
@@ -889,18 +942,18 @@ def test_function(arg):
                 pass
         """
         )
-        result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
-        node = dom.find_first_by_tag("testsuite")
-        pnode = node.find_first_by_tag("testcase")
+        result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
+        node = dom.get_first_by_tag("testsuite")
+        pnode = node.get_first_by_tag("testcase")
         if junit_logging == "no":
-            assert not node.find_by_tag(
-                "system-err"
-            ), "system-err should not be generated"
+            assert not node.find_by_tag("system-err"), (
+                "system-err should not be generated"
+            )
         if junit_logging == "system-err":
-            systemerr = pnode.find_first_by_tag("system-err")
-            assert (
-                "hello-stderr" in systemerr.toxml()
-            ), "'hello-stderr' should be in system-err"
+            systemerr = pnode.get_first_by_tag("system-err")
+            assert "hello-stderr" in systemerr.toxml(), (
+                "'hello-stderr' should be in system-err"
+            )
 
     @pytest.mark.parametrize("junit_logging", ["no", "system-out"])
     def test_avoid_double_stdout(
@@ -920,15 +973,15 @@ def test_function(arg):
                 sys.stdout.write('hello-stdout call')
         """
         )
-        result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging)
-        node = dom.find_first_by_tag("testsuite")
-        pnode = node.find_first_by_tag("testcase")
+        result, dom = run_and_parse("-o", f"junit_logging={junit_logging}")
+        node = dom.get_first_by_tag("testsuite")
+        pnode = node.get_first_by_tag("testcase")
         if junit_logging == "no":
-            assert not node.find_by_tag(
-                "system-out"
-            ), "system-out should not be generated"
+            assert not node.find_by_tag("system-out"), (
+                "system-out should not be generated"
+            )
         if junit_logging == "system-out":
-            systemout = pnode.find_first_by_tag("system-out")
+            systemout = pnode.get_first_by_tag("system-out")
             assert "hello-stdout call" in systemout.toxml()
             assert "hello-stdout teardown" in systemout.toxml()
 
@@ -942,18 +995,18 @@ def test_mangle_test_address() -> None:
 
 
 def test_dont_configure_on_workers(tmp_path: Path) -> None:
-    gotten: List[object] = []
+    gotten: list[object] = []
 
     class FakeConfig:
         if TYPE_CHECKING:
             workerinput = None
 
-        def __init__(self):
+        def __init__(self) -> None:
             self.pluginmanager = self
             self.option = self
             self.stash = Stash()
 
-        def getini(self, name):
+        def getini(self, name: str) -> str:
             return "pytest"
 
         junitprefix = None
@@ -989,21 +1042,21 @@ def repr_failure(self, excinfo):
                     return "custom item runtest failed"
         """
         )
-        pytester.path.joinpath("myfile.xyz").write_text("hello")
+        pytester.path.joinpath("myfile.xyz").write_text("hello", encoding="utf-8")
         result, dom = run_and_parse(family=xunit_family)
         assert result.ret
-        node = dom.find_first_by_tag("testsuite")
+        node = dom.get_first_by_tag("testsuite")
         node.assert_attr(errors=0, failures=1, skipped=0, tests=1)
-        tnode = node.find_first_by_tag("testcase")
+        tnode = node.get_first_by_tag("testcase")
         tnode.assert_attr(name="myfile.xyz")
-        fnode = tnode.find_first_by_tag("failure")
+        fnode = tnode.get_first_by_tag("failure")
         fnode.assert_attr(message="custom item runtest failed")
         assert "custom item runtest failed" in fnode.toxml()
 
 
 @pytest.mark.parametrize("junit_logging", ["no", "system-out"])
 def test_nullbyte(pytester: Pytester, junit_logging: str) -> None:
-    # A null byte can not occur in XML (see section 2.2 of the spec)
+    # A null byte cannot occur in XML (see section 2.2 of the spec)
     pytester.makepyfile(
         """
         import sys
@@ -1014,8 +1067,8 @@ def test_print_nullbyte():
     """
     )
     xmlf = pytester.path.joinpath("junit.xml")
-    pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging)
-    text = xmlf.read_text()
+    pytester.runpytest(f"--junitxml={xmlf}", "-o", f"junit_logging={junit_logging}")
+    text = xmlf.read_text(encoding="utf-8")
     assert "\x00" not in text
     if junit_logging == "system-out":
         assert "#x00" in text
@@ -1036,8 +1089,8 @@ def test_print_nullbyte():
     """
     )
     xmlf = pytester.path.joinpath("junit.xml")
-    pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging)
-    text = xmlf.read_text()
+    pytester.runpytest(f"--junitxml={xmlf}", "-o", f"junit_logging={junit_logging}")
+    text = xmlf.read_text(encoding="utf-8")
     if junit_logging == "system-out":
         assert "#x0" in text
     if junit_logging == "no":
@@ -1072,9 +1125,9 @@ def test_invalid_xml_escape() -> None:
     for i in invalid:
         got = bin_xml_escape(chr(i))
         if i <= 0xFF:
-            expected = "#x%02X" % i
+            expected = f"#x{i:02X}"
         else:
-            expected = "#x%04X" % i
+            expected = f"#x{i:04X}"
         assert got == expected
     for i in valid:
         assert chr(i) == bin_xml_escape(chr(i))
@@ -1137,7 +1190,7 @@ def test_func(char):
     )
     result, dom = run_and_parse()
     assert result.ret == 0
-    node = dom.find_first_by_tag("testcase")
+    node = dom.get_first_by_tag("testcase")
     node.assert_attr(name="test_func[\\x00]")
 
 
@@ -1154,7 +1207,7 @@ def test_func(param):
     )
     result, dom = run_and_parse()
     assert result.ret == 0
-    node = dom.find_first_by_tag("testcase")
+    node = dom.get_first_by_tag("testcase")
     node.assert_attr(classname="test_double_colon_split_function_issue469")
     node.assert_attr(name="test_func[double::colon]")
 
@@ -1173,7 +1226,7 @@ def test_func(self, param):
     )
     result, dom = run_and_parse()
     assert result.ret == 0
-    node = dom.find_first_by_tag("testcase")
+    node = dom.get_first_by_tag("testcase")
     node.assert_attr(classname="test_double_colon_split_method_issue469.TestClass")
     node.assert_attr(name="test_func[double::colon]")
 
@@ -1185,7 +1238,7 @@ def test_unicode_issue368(pytester: Pytester) -> None:
 
     class Report(BaseReport):
         longrepr = ustr
-        sections: List[Tuple[str, str]] = []
+        sections: list[tuple[str, str]] = []
         nodeid = "something"
         location = "tests/filename.py", 42, "TestClass.method"
         when = "teardown"
@@ -1203,7 +1256,7 @@ class Report(BaseReport):
     node_reporter.append_skipped(test_report)
     test_report.longrepr = "filename", 1, "Skipped: 卡嘣嘣"
     node_reporter.append_skipped(test_report)
-    test_report.wasxfail = ustr  # type: ignore[attr-defined]
+    test_report.wasxfail = ustr
     node_reporter.append_skipped(test_report)
     log.pytest_sessionfinish()
 
@@ -1221,15 +1274,45 @@ def test_record(record_property, other):
     """
     )
     result, dom = run_and_parse()
-    node = dom.find_first_by_tag("testsuite")
-    tnode = node.find_first_by_tag("testcase")
-    psnode = tnode.find_first_by_tag("properties")
+    node = dom.get_first_by_tag("testsuite")
+    tnode = node.get_first_by_tag("testcase")
+    psnode = tnode.get_first_by_tag("properties")
     pnodes = psnode.find_by_tag("property")
     pnodes[0].assert_attr(name="bar", value="1")
     pnodes[1].assert_attr(name="foo", value="<1")
     result.stdout.fnmatch_lines(["*= 1 passed in *"])
 
 
+def test_record_property_on_test_and_teardown_failure(
+    pytester: Pytester, run_and_parse: RunAndParse
+) -> None:
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture
+        def other(record_property):
+            record_property("bar", 1)
+            yield
+            assert 0
+
+        def test_record(record_property, other):
+            record_property("foo", "<1")
+            assert 0
+    """
+    )
+    result, dom = run_and_parse()
+    node = dom.get_first_by_tag("testsuite")
+    tnodes = node.find_by_tag("testcase")
+    for tnode in tnodes:
+        psnode = tnode.get_first_by_tag("properties")
+        assert psnode, f"testcase didn't had expected properties:\n{tnode}"
+        pnodes = psnode.find_by_tag("property")
+        pnodes[0].assert_attr(name="bar", value="1")
+        pnodes[1].assert_attr(name="foo", value="<1")
+    result.stdout.fnmatch_lines(["*= 1 failed, 1 error *"])
+
+
 def test_record_property_same_name(
     pytester: Pytester, run_and_parse: RunAndParse
 ) -> None:
@@ -1241,9 +1324,9 @@ def test_record_with_same_name(record_property):
     """
     )
     result, dom = run_and_parse()
-    node = dom.find_first_by_tag("testsuite")
-    tnode = node.find_first_by_tag("testcase")
-    psnode = tnode.find_first_by_tag("properties")
+    node = dom.get_first_by_tag("testsuite")
+    tnode = node.get_first_by_tag("testcase")
+    psnode = tnode.get_first_by_tag("properties")
     pnodes = psnode.find_by_tag("property")
     pnodes[0].assert_attr(name="foo", value="bar")
     pnodes[1].assert_attr(name="foo", value="baz")
@@ -1254,12 +1337,10 @@ def test_record_fixtures_without_junitxml(
     pytester: Pytester, fixture_name: str
 ) -> None:
     pytester.makepyfile(
-        """
+        f"""
         def test_record({fixture_name}):
             {fixture_name}("foo", "bar")
-    """.format(
-            fixture_name=fixture_name
-        )
+    """
     )
     result = pytester.runpytest()
     assert result.ret == 0
@@ -1285,8 +1366,8 @@ def test_record(record_xml_attribute, other):
     """
     )
     result, dom = run_and_parse()
-    node = dom.find_first_by_tag("testsuite")
-    tnode = node.find_first_by_tag("testcase")
+    node = dom.get_first_by_tag("testsuite")
+    tnode = node.get_first_by_tag("testcase")
     tnode.assert_attr(bar="1")
     tnode.assert_attr(foo="<1")
     result.stdout.fnmatch_lines(
@@ -1307,7 +1388,7 @@ def test_record_fixtures_xunit2(
     """
     )
     pytester.makepyfile(
-        """
+        f"""
         import pytest
 
         @pytest.fixture
@@ -1315,9 +1396,7 @@ def other({fixture_name}):
             {fixture_name}("bar", 1)
         def test_record({fixture_name}, other):
             {fixture_name}("foo", "<1");
-    """.format(
-            fixture_name=fixture_name
-        )
+    """
     )
 
     result, dom = run_and_parse(family=None)
@@ -1327,10 +1406,8 @@ def test_record({fixture_name}, other):
             "*test_record_fixtures_xunit2.py:6:*record_xml_attribute is an experimental feature"
         )
     expected_lines = [
-        "*test_record_fixtures_xunit2.py:6:*{fixture_name} is incompatible "
-        "with junit_family 'xunit2' (use 'legacy' or 'xunit1')".format(
-            fixture_name=fixture_name
-        )
+        f"*test_record_fixtures_xunit2.py:6:*{fixture_name} is incompatible "
+        "with junit_family 'xunit2' (use 'legacy' or 'xunit1')"
     ]
     result.stdout.fnmatch_lines(expected_lines)
 
@@ -1352,7 +1429,7 @@ def test_x(i):
     """
     )
     _, dom = run_and_parse("-n2")
-    suite_node = dom.find_first_by_tag("testsuite")
+    suite_node = dom.get_first_by_tag("testsuite")
     failed = []
     for case_node in suite_node.find_by_tag("testcase"):
         if case_node.find_first_by_tag("failure"):
@@ -1447,7 +1524,12 @@ def test_pass():
 
     result.stdout.no_fnmatch_line("*INTERNALERROR*")
 
-    items = sorted("%(classname)s %(name)s" % x for x in dom.find_by_tag("testcase"))
+    items = sorted(
+        f"{x['classname']} {x['name']}"
+        # dom is a DomNode not a mapping, it's not possible to ** it.
+        for x in dom.find_by_tag("testcase")
+    )
+
     import pprint
 
     pprint.pprint(items)
@@ -1468,7 +1550,7 @@ def test_global_properties(pytester: Pytester, xunit_family: str) -> None:
     log = LogXML(str(path), None, family=xunit_family)
 
     class Report(BaseReport):
-        sections: List[Tuple[str, str]] = []
+        sections: list[tuple[str, str]] = []
         nodeid = "test_node_id"
 
     log.pytest_sessionstart()
@@ -1504,7 +1586,7 @@ def test_url_property(pytester: Pytester) -> None:
 
     class Report(BaseReport):
         longrepr = "FooBarBaz"
-        sections: List[Tuple[str, str]] = []
+        sections: list[tuple[str, str]] = []
         nodeid = "something"
         location = "tests/filename.py", 42, "TestClass.method"
         url = test_url
@@ -1518,9 +1600,9 @@ class Report(BaseReport):
 
     test_case = minidom.parse(str(path)).getElementsByTagName("testcase")[0]
 
-    assert (
-        test_case.getAttribute("url") == test_url
-    ), "The URL did not get written to the xml"
+    assert test_case.getAttribute("url") == test_url, (
+        "The URL did not get written to the xml"
+    )
 
 
 @parametrize_families
@@ -1538,10 +1620,11 @@ def test_func2(record_testsuite_property):
     )
     result, dom = run_and_parse(family=xunit_family)
     assert result.ret == 0
-    node = dom.find_first_by_tag("testsuite")
-    properties_node = node.find_first_by_tag("properties")
-    p1_node = properties_node.find_nth_by_tag("property", 0)
-    p2_node = properties_node.find_nth_by_tag("property", 1)
+    node = dom.get_first_by_tag("testsuite")
+    properties_node = node.get_first_by_tag("properties")
+    p1_node, p2_node = properties_node.find_by_tag(
+        "property",
+    )[:2]
     p1_node.assert_attr(name="stats", value="all good")
     p2_node.assert_attr(name="stats", value="10")
 
@@ -1582,13 +1665,11 @@ def test_set_suite_name(
 ) -> None:
     if suite_name:
         pytester.makeini(
-            """
+            f"""
             [pytest]
             junit_suite_name={suite_name}
-            junit_family={family}
-        """.format(
-                suite_name=suite_name, family=xunit_family
-            )
+            junit_family={xunit_family}
+        """
         )
         expected = suite_name
     else:
@@ -1603,7 +1684,7 @@ def test_func():
     )
     result, dom = run_and_parse(family=xunit_family)
     assert result.ret == 0
-    node = dom.find_first_by_tag("testsuite")
+    node = dom.get_first_by_tag("testsuite")
     node.assert_attr(name=expected)
 
 
@@ -1619,25 +1700,62 @@ def test_skip():
     """
     )
     _, dom = run_and_parse()
-    node = dom.find_first_by_tag("testcase")
-    snode = node.find_first_by_tag("skipped")
+    node = dom.get_first_by_tag("testcase")
+    snode = node.get_first_by_tag("skipped")
     assert "1 <> 2" in snode.text
     snode.assert_attr(message="1 <> 2")
 
 
+def test_bin_escaped_skipreason(pytester: Pytester, run_and_parse: RunAndParse) -> None:
+    """Escape special characters from mark.skip reason (#11842)."""
+    pytester.makepyfile(
+        """
+        import pytest
+        @pytest.mark.skip("\33[31;1mred\33[0m")
+        def test_skip():
+            pass
+    """
+    )
+    _, dom = run_and_parse()
+    node = dom.get_first_by_tag("testcase")
+    snode = node.get_first_by_tag("skipped")
+    assert "#x1B[31;1mred#x1B[0m" in snode.text
+    snode.assert_attr(message="#x1B[31;1mred#x1B[0m")
+
+
+def test_escaped_setup_teardown_error(
+    pytester: Pytester, run_and_parse: RunAndParse
+) -> None:
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture()
+        def my_setup():
+            raise Exception("error: \033[31mred\033[m")
+
+        def test_esc(my_setup):
+            pass
+    """
+    )
+    _, dom = run_and_parse()
+    node = dom.get_first_by_tag("testcase")
+    snode = node.get_first_by_tag("error")
+    assert "#x1B[31mred#x1B[m" in snode["message"]
+    assert "#x1B[31mred#x1B[m" in snode.text
+
+
 @parametrize_families
 def test_logging_passing_tests_disabled_does_not_log_test_output(
     pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str
 ) -> None:
     pytester.makeini(
-        """
+        f"""
         [pytest]
         junit_log_passing_tests=False
         junit_logging=system-out
-        junit_family={family}
-    """.format(
-            family=xunit_family
-        )
+        junit_family={xunit_family}
+    """
     )
     pytester.makepyfile(
         """
@@ -1653,7 +1771,7 @@ def test_func():
     )
     result, dom = run_and_parse(family=xunit_family)
     assert result.ret == 0
-    node = dom.find_first_by_tag("testcase")
+    node = dom.get_first_by_tag("testcase")
     assert len(node.find_by_tag("system-err")) == 0
     assert len(node.find_by_tag("system-out")) == 0
 
@@ -1667,13 +1785,11 @@ def test_logging_passing_tests_disabled_logs_output_for_failing_test_issue5430(
     xunit_family: str,
 ) -> None:
     pytester.makeini(
-        """
+        f"""
         [pytest]
         junit_log_passing_tests=False
-        junit_family={family}
-    """.format(
-            family=xunit_family
-        )
+        junit_family={xunit_family}
+    """
     )
     pytester.makepyfile(
         """
@@ -1687,10 +1803,10 @@ def test_func():
     """
     )
     result, dom = run_and_parse(
-        "-o", "junit_logging=%s" % junit_logging, family=xunit_family
+        "-o", f"junit_logging={junit_logging}", family=xunit_family
     )
     assert result.ret == 1
-    node = dom.find_first_by_tag("testcase")
+    node = dom.get_first_by_tag("testcase")
     if junit_logging == "system-out":
         assert len(node.find_by_tag("system-err")) == 0
         assert len(node.find_by_tag("system-out")) == 1
diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py
index 9ab139df467..72854e4e5c0 100644
--- a/testing/test_legacypath.py
+++ b/testing/test_legacypath.py
@@ -1,9 +1,13 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from pathlib import Path
 
-import pytest
 from _pytest.compat import LEGACY_PATH
+from _pytest.fixtures import TopRequest
 from _pytest.legacypath import TempdirFactory
 from _pytest.legacypath import Testdir
+import pytest
 
 
 def test_item_fspath(pytester: pytest.Pytester) -> None:
@@ -14,7 +18,7 @@ def test_item_fspath(pytester: pytest.Pytester) -> None:
     items2, hookrec = pytester.inline_genitems(item.nodeid)
     (item2,) = items2
     assert item2.name == item.name
-    assert item2.fspath == item.fspath  # type: ignore[attr-defined]
+    assert item2.fspath == item.fspath
     assert item2.path == item.path
 
 
@@ -77,7 +81,7 @@ def test_1(tmpdir):
             assert os.path.realpath(str(tmpdir)) == str(tmpdir)
     """
     )
-    result = pytester.runpytest("-s", p, "--basetemp=%s/bt" % linktemp)
+    result = pytester.runpytest("-s", p, f"--basetemp={linktemp}/bt")
     assert not result.ret
 
 
@@ -90,7 +94,8 @@ def test_cache_makedir(cache: pytest.Cache) -> None:
 def test_fixturerequest_getmodulepath(pytester: pytest.Pytester) -> None:
     modcol = pytester.getmodulecol("def test_somefunc(): pass")
     (item,) = pytester.genitems([modcol])
-    req = pytest.FixtureRequest(item, _ispytest=True)
+    assert isinstance(item, pytest.Function)
+    req = TopRequest(item, _ispytest=True)
     assert req.path == modcol.path
     assert req.fspath == modcol.fspath  # type: ignore[attr-defined]
 
@@ -105,7 +110,7 @@ def test_session_scoped_unavailable_attributes(self, session_request):
             AttributeError,
             match="path not available in session-scoped context",
         ):
-            session_request.fspath
+            _ = session_request.fspath
 
 
 @pytest.mark.parametrize("config_type", ["ini", "pyproject"])
@@ -152,7 +157,7 @@ def pytest_addoption(parser):
     )
     pytester.makepyfile(
         r"""
-        def test_overriden(pytestconfig):
+        def test_overridden(pytestconfig):
             config_paths = pytestconfig.getini("paths")
             print(config_paths)
             for cpf in config_paths:
@@ -161,3 +166,20 @@ def test_overriden(pytestconfig):
     )
     result = pytester.runpytest("--override-ini", "paths=foo/bar1.py foo/bar2.py", "-s")
     result.stdout.fnmatch_lines(["user_path:bar1.py", "user_path:bar2.py"])
+
+
+def test_inifile_from_cmdline_main_hook(pytester: pytest.Pytester) -> None:
+    """Ensure Config.inifile is available during pytest_cmdline_main (#9396)."""
+    p = pytester.makeini(
+        """
+        [pytest]
+        """
+    )
+    pytester.makeconftest(
+        """
+        def pytest_cmdline_main(config):
+            print("pytest_cmdline_main inifile =", config.inifile)
+        """
+    )
+    result = pytester.runpytest_subprocess("-s")
+    result.stdout.fnmatch_lines(f"*pytest_cmdline_main inifile = {p}")
diff --git a/testing/test_link_resolve.py b/testing/test_link_resolve.py
index 60a86ada36e..0557dae669d 100644
--- a/testing/test_link_resolve.py
+++ b/testing/test_link_resolve.py
@@ -1,10 +1,13 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from contextlib import contextmanager
 import os.path
+from pathlib import Path
+from string import ascii_lowercase
 import subprocess
 import sys
 import textwrap
-from contextlib import contextmanager
-from pathlib import Path
-from string import ascii_lowercase
 
 from _pytest.pytester import Pytester
 
@@ -59,7 +62,8 @@ def test_link_resolve(pytester: Pytester) -> None:
         def test_foo():
             raise AssertionError()
         """
-        )
+        ),
+        encoding="utf-8",
     )
 
     subst = subst_path_linux
diff --git a/testing/test_main.py b/testing/test_main.py
index 6a13633f6a9..94eac02ce63 100644
--- a/testing/test_main.py
+++ b/testing/test_main.py
@@ -1,15 +1,18 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import argparse
 import os
-import re
 from pathlib import Path
-from typing import Optional
+import re
 
-import pytest
 from _pytest.config import ExitCode
 from _pytest.config import UsageError
+from _pytest.main import CollectionArgument
 from _pytest.main import resolve_collection_argument
 from _pytest.main import validate_basetemp
 from _pytest.pytester import Pytester
+import pytest
 
 
 @pytest.mark.parametrize(
@@ -23,19 +26,17 @@
 def test_wrap_session_notify_exception(ret_exc, pytester: Pytester) -> None:
     returncode, exc = ret_exc
     c1 = pytester.makeconftest(
-        """
+        f"""
         import pytest
 
         def pytest_sessionstart():
-            raise {exc}("boom")
+            raise {exc.__name__}("boom")
 
         def pytest_internalerror(excrepr, excinfo):
             returncode = {returncode!r}
             if returncode is not False:
                 pytest.exit("exiting after %s..." % excinfo.typename, returncode={returncode!r})
-    """.format(
-            returncode=returncode, exc=exc.__name__
-        )
+    """
     )
     result = pytester.runpytest()
     if returncode:
@@ -44,14 +45,16 @@ def pytest_internalerror(excrepr, excinfo):
         assert result.ret == ExitCode.INTERNAL_ERROR
     assert result.stdout.lines[0] == "INTERNALERROR> Traceback (most recent call last):"
 
+    end_lines = result.stdout.lines[-3:]
+
     if exc == SystemExit:
-        assert result.stdout.lines[-3:] == [
+        assert end_lines == [
             f'INTERNALERROR>   File "{c1}", line 4, in pytest_sessionstart',
             'INTERNALERROR>     raise SystemExit("boom")',
             "INTERNALERROR> SystemExit: boom",
         ]
     else:
-        assert result.stdout.lines[-3:] == [
+        assert end_lines == [
             f'INTERNALERROR>   File "{c1}", line 4, in pytest_sessionstart',
             'INTERNALERROR>     raise ValueError("boom")',
             "INTERNALERROR> ValueError: boom",
@@ -64,16 +67,14 @@ def pytest_internalerror(excrepr, excinfo):
 
 @pytest.mark.parametrize("returncode", (None, 42))
 def test_wrap_session_exit_sessionfinish(
-    returncode: Optional[int], pytester: Pytester
+    returncode: int | None, pytester: Pytester
 ) -> None:
     pytester.makeconftest(
-        """
+        f"""
         import pytest
         def pytest_sessionfinish():
             pytest.exit(reason="exit_pytest_sessionfinish", returncode={returncode})
-    """.format(
-            returncode=returncode
-        )
+    """
     )
     result = pytester.runpytest()
     if returncode:
@@ -119,26 +120,43 @@ def invocation_path(self, pytester: Pytester) -> Path:
 
     def test_file(self, invocation_path: Path) -> None:
         """File and parts."""
-        assert resolve_collection_argument(invocation_path, "src/pkg/test.py") == (
-            invocation_path / "src/pkg/test.py",
-            [],
+        assert resolve_collection_argument(
+            invocation_path, "src/pkg/test.py"
+        ) == CollectionArgument(
+            path=invocation_path / "src/pkg/test.py",
+            parts=[],
+            module_name=None,
         )
-        assert resolve_collection_argument(invocation_path, "src/pkg/test.py::") == (
-            invocation_path / "src/pkg/test.py",
-            [""],
+        assert resolve_collection_argument(
+            invocation_path, "src/pkg/test.py::"
+        ) == CollectionArgument(
+            path=invocation_path / "src/pkg/test.py",
+            parts=[""],
+            module_name=None,
         )
         assert resolve_collection_argument(
             invocation_path, "src/pkg/test.py::foo::bar"
-        ) == (invocation_path / "src/pkg/test.py", ["foo", "bar"])
+        ) == CollectionArgument(
+            path=invocation_path / "src/pkg/test.py",
+            parts=["foo", "bar"],
+            module_name=None,
+        )
         assert resolve_collection_argument(
             invocation_path, "src/pkg/test.py::foo::bar::"
-        ) == (invocation_path / "src/pkg/test.py", ["foo", "bar", ""])
+        ) == CollectionArgument(
+            path=invocation_path / "src/pkg/test.py",
+            parts=["foo", "bar", ""],
+            module_name=None,
+        )
 
     def test_dir(self, invocation_path: Path) -> None:
         """Directory and parts."""
-        assert resolve_collection_argument(invocation_path, "src/pkg") == (
-            invocation_path / "src/pkg",
-            [],
+        assert resolve_collection_argument(
+            invocation_path, "src/pkg"
+        ) == CollectionArgument(
+            path=invocation_path / "src/pkg",
+            parts=[],
+            module_name=None,
         )
 
         with pytest.raises(
@@ -155,13 +173,24 @@ def test_pypath(self, invocation_path: Path) -> None:
         """Dotted name and parts."""
         assert resolve_collection_argument(
             invocation_path, "pkg.test", as_pypath=True
-        ) == (invocation_path / "src/pkg/test.py", [])
+        ) == CollectionArgument(
+            path=invocation_path / "src/pkg/test.py",
+            parts=[],
+            module_name="pkg.test",
+        )
         assert resolve_collection_argument(
             invocation_path, "pkg.test::foo::bar", as_pypath=True
-        ) == (invocation_path / "src/pkg/test.py", ["foo", "bar"])
-        assert resolve_collection_argument(invocation_path, "pkg", as_pypath=True) == (
-            invocation_path / "src/pkg",
-            [],
+        ) == CollectionArgument(
+            path=invocation_path / "src/pkg/test.py",
+            parts=["foo", "bar"],
+            module_name="pkg.test",
+        )
+        assert resolve_collection_argument(
+            invocation_path, "pkg", as_pypath=True
+        ) == CollectionArgument(
+            path=invocation_path / "src/pkg",
+            parts=[],
+            module_name="pkg",
         )
 
         with pytest.raises(
@@ -171,6 +200,15 @@ def test_pypath(self, invocation_path: Path) -> None:
                 invocation_path, "pkg::foo::bar", as_pypath=True
             )
 
+    def test_parametrized_name_with_colons(self, invocation_path: Path) -> None:
+        assert resolve_collection_argument(
+            invocation_path, "src/pkg/test.py::test[a::b]"
+        ) == CollectionArgument(
+            path=invocation_path / "src/pkg/test.py",
+            parts=["test[a::b]"],
+            module_name=None,
+        )
+
     def test_does_not_exist(self, invocation_path: Path) -> None:
         """Given a file/module that does not exist raises UsageError."""
         with pytest.raises(
@@ -189,9 +227,12 @@ def test_does_not_exist(self, invocation_path: Path) -> None:
     def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> None:
         """Absolute paths resolve back to absolute paths."""
         full_path = str(invocation_path / "src")
-        assert resolve_collection_argument(invocation_path, full_path) == (
-            Path(os.path.abspath("src")),
-            [],
+        assert resolve_collection_argument(
+            invocation_path, full_path
+        ) == CollectionArgument(
+            path=Path(os.path.abspath("src")),
+            parts=[],
+            module_name=None,
         )
 
         # ensure full paths given in the command-line without the drive letter resolve
@@ -199,7 +240,11 @@ def test_absolute_paths_are_resolved_correctly(self, invocation_path: Path) -> N
         drive, full_path_without_drive = os.path.splitdrive(full_path)
         assert resolve_collection_argument(
             invocation_path, full_path_without_drive
-        ) == (Path(os.path.abspath("src")), [])
+        ) == CollectionArgument(
+            path=Path(os.path.abspath("src")),
+            parts=[],
+            module_name=None,
+        )
 
 
 def test_module_full_path_without_drive(pytester: Pytester) -> None:
@@ -239,3 +284,34 @@ def test(fix):
             "* 1 passed in *",
         ]
     )
+
+
+def test_very_long_cmdline_arg(pytester: Pytester) -> None:
+    """
+    Regression test for #11394.
+
+    Note: we could not manage to actually reproduce the error with this code, we suspect
+    GitHub runners are configured to support very long paths, however decided to leave
+    the test in place in case this ever regresses in the future.
+    """
+    pytester.makeconftest(
+        """
+        import pytest
+
+        def pytest_addoption(parser):
+            parser.addoption("--long-list", dest="long_list", action="store", default="all", help="List of things")
+
+        @pytest.fixture(scope="module")
+        def specified_feeds(request):
+            list_string = request.config.getoption("--long-list")
+            return list_string.split(',')
+        """
+    )
+    pytester.makepyfile(
+        """
+        def test_foo(specified_feeds):
+            assert len(specified_feeds) == 100_000
+        """
+    )
+    result = pytester.runpytest("--long-list", ",".join(["helloworld"] * 100_000))
+    result.stdout.fnmatch_lines("* 1 passed *")
diff --git a/testing/test_mark.py b/testing/test_mark.py
index da67d1ea7bc..1e51f9db18f 100644
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -1,23 +1,24 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import os
 import sys
-from typing import List
-from typing import Optional
 from unittest import mock
 
-import pytest
 from _pytest.config import ExitCode
 from _pytest.mark import MarkGenerator
 from _pytest.mark.structures import EMPTY_PARAMETERSET_OPTION
 from _pytest.nodes import Collector
 from _pytest.nodes import Node
 from _pytest.pytester import Pytester
+import pytest
 
 
 class TestMark:
     @pytest.mark.parametrize("attr", ["mark", "param"])
     def test_pytest_exists_in_namespace_all(self, attr: str) -> None:
         module = sys.modules["pytest"]
-        assert attr in module.__all__  # type: ignore
+        assert attr in module.__all__
 
     def test_pytest_mark_notcallable(self) -> None:
         mark = MarkGenerator(_ispytest=True)
@@ -33,7 +34,7 @@ class SomeClass:
 
         assert pytest.mark.foo(some_function) is some_function
         marked_with_args = pytest.mark.foo.with_args(some_function)
-        assert marked_with_args is not some_function  # type: ignore[comparison-overlap]
+        assert marked_with_args is not some_function
 
         assert pytest.mark.foo(SomeClass) is SomeClass
         assert pytest.mark.foo.with_args(SomeClass) is not SomeClass  # type: ignore[comparison-overlap]
@@ -41,7 +42,7 @@ class SomeClass:
     def test_pytest_mark_name_starts_with_underscore(self) -> None:
         mark = MarkGenerator(_ispytest=True)
         with pytest.raises(AttributeError):
-            mark._some_name
+            _ = mark._some_name
 
 
 def test_marked_class_run_twice(pytester: Pytester) -> None:
@@ -213,7 +214,7 @@ def test_hello():
     ],
 )
 def test_mark_option(
-    expr: str, expected_passed: List[Optional[str]], pytester: Pytester
+    expr: str, expected_passed: list[str | None], pytester: Pytester
 ) -> None:
     pytester.makepyfile(
         """
@@ -232,12 +233,60 @@ def test_two():
     assert passed_str == expected_passed
 
 
+@pytest.mark.parametrize(
+    ("expr", "expected_passed"),
+    [
+        ("car(color='red')", ["test_one"]),
+        ("car(color='red') or car(color='blue')", ["test_one", "test_two"]),
+        ("car and not car(temp=5)", ["test_one", "test_three"]),
+        ("car(temp=4)", ["test_one"]),
+        ("car(temp=4) or car(temp=5)", ["test_one", "test_two"]),
+        ("car(temp=4) and car(temp=5)", []),
+        ("car(temp=-5)", ["test_three"]),
+        ("car(ac=True)", ["test_one"]),
+        ("car(ac=False)", ["test_two"]),
+        ("car(ac=None)", ["test_three"]),  # test NOT_NONE_SENTINEL
+    ],
+    ids=str,
+)
+def test_mark_option_with_kwargs(
+    expr: str, expected_passed: list[str | None], pytester: Pytester
+) -> None:
+    pytester.makepyfile(
+        """
+        import pytest
+        @pytest.mark.car
+        @pytest.mark.car(ac=True)
+        @pytest.mark.car(temp=4)
+        @pytest.mark.car(color="red")
+        def test_one():
+            pass
+        @pytest.mark.car
+        @pytest.mark.car(ac=False)
+        @pytest.mark.car(temp=5)
+        @pytest.mark.car(color="blue")
+        def test_two():
+            pass
+        @pytest.mark.car
+        @pytest.mark.car(ac=None)
+        @pytest.mark.car(temp=-5)
+        def test_three():
+            pass
+
+    """
+    )
+    rec = pytester.inline_run("-m", expr)
+    passed, skipped, fail = rec.listoutcomes()
+    passed_str = [x.nodeid.split("::")[-1] for x in passed]
+    assert passed_str == expected_passed
+
+
 @pytest.mark.parametrize(
     ("expr", "expected_passed"),
     [("interface", ["test_interface"]), ("not interface", ["test_nointer"])],
 )
 def test_mark_option_custom(
-    expr: str, expected_passed: List[str], pytester: Pytester
+    expr: str, expected_passed: list[str], pytester: Pytester
 ) -> None:
     pytester.makeconftest(
         """
@@ -275,7 +324,7 @@ def test_nointer():
     ],
 )
 def test_keyword_option_custom(
-    expr: str, expected_passed: List[str], pytester: Pytester
+    expr: str, expected_passed: list[str], pytester: Pytester
 ) -> None:
     pytester.makepyfile(
         """
@@ -313,7 +362,7 @@ def test_keyword_option_considers_mark(pytester: Pytester) -> None:
     ],
 )
 def test_keyword_option_parametrize(
-    expr: str, expected_passed: List[str], pytester: Pytester
+    expr: str, expected_passed: list[str], pytester: Pytester
 ) -> None:
     pytester.makepyfile(
         """
@@ -371,6 +420,10 @@ def test_func(arg):
             "not or",
             "at column 5: expected not OR left parenthesis OR identifier; got or",
         ),
+        (
+            "nonexistent_mark(non_supported='kwarg')",
+            "Keyword expressions do not support call parameters",
+        ),
     ],
 )
 def test_keyword_option_wrong_arguments(
@@ -806,12 +859,12 @@ def test_2(self):
         pytester.makepyfile(
             conftest="""
             import pytest
-            @pytest.hookimpl(hookwrapper=True)
+            @pytest.hookimpl(wrapper=True)
             def pytest_pycollect_makeitem(name):
-                outcome = yield
+                item = yield
                 if name == "TestClass":
-                    item = outcome.get_result()
                     item.extra_keyword_matches.add("xxx")
+                return item
         """
         )
         reprec = pytester.inline_run(p.parent, "-s", "-k", keyword)
@@ -823,25 +876,6 @@ def pytest_pycollect_makeitem(name):
         assert len(dlist) == 1
         assert dlist[0].items[0].name == "test_1"
 
-    def test_select_starton(self, pytester: Pytester) -> None:
-        threepass = pytester.makepyfile(
-            test_threepass="""
-            def test_one(): assert 1
-            def test_two(): assert 1
-            def test_three(): assert 1
-        """
-        )
-        reprec = pytester.inline_run(
-            "-Wignore::pytest.PytestRemovedIn7Warning", "-k", "test_two:", threepass
-        )
-        passed, skipped, failed = reprec.listoutcomes()
-        assert len(passed) == 2
-        assert not failed
-        dlist = reprec.getcalls("pytest_deselected")
-        assert len(dlist) == 1
-        item = dlist[0].items[0]
-        assert item.name == "test_one"
-
     def test_keyword_extra(self, pytester: Pytester) -> None:
         p = pytester.makepyfile(
             """
@@ -890,17 +924,30 @@ def test_one(): assert 1
         deselected_tests = dlist[0].items
         assert len(deselected_tests) == 1
 
-    def test_no_match_directories_outside_the_suite(self, pytester: Pytester) -> None:
+    def test_no_match_directories_outside_the_suite(
+        self,
+        pytester: Pytester,
+        monkeypatch: pytest.MonkeyPatch,
+    ) -> None:
         """`-k` should not match against directories containing the test suite (#7040)."""
-        test_contents = """
-            def test_aaa(): pass
-            def test_ddd(): pass
-        """
+        pytester.makefile(
+            **{
+                "suite/pytest": """[pytest]""",
+            },
+            ext=".ini",
+        )
         pytester.makepyfile(
-            **{"ddd/tests/__init__.py": "", "ddd/tests/test_foo.py": test_contents}
+            **{
+                "suite/ddd/tests/__init__.py": "",
+                "suite/ddd/tests/test_foo.py": """
+                def test_aaa(): pass
+                def test_ddd(): pass
+            """,
+            }
         )
+        monkeypatch.chdir(pytester.path / "suite")
 
-        def get_collected_names(*args):
+        def get_collected_names(*args: str) -> list[str]:
             _, rec = pytester.inline_genitems(*args)
             calls = rec.getcalls("pytest_collection_finish")
             assert len(calls) == 1
@@ -912,12 +959,6 @@ def get_collected_names(*args):
         # do not collect anything based on names outside the collection tree
         assert get_collected_names("-k", pytester._name) == []
 
-        # "-k ddd" should only collect "test_ddd", but not
-        # 'test_aaa' just because one of its parent directories is named "ddd";
-        # this was matched previously because Package.name would contain the full path
-        # to the package
-        assert get_collected_names("-k", "ddd") == ["test_ddd"]
-
 
 class TestMarkDecorator:
     @pytest.mark.parametrize(
@@ -941,20 +982,19 @@ def test_aliases(self) -> None:
 
 @pytest.mark.parametrize("mark", [None, "", "skip", "xfail"])
 def test_parameterset_for_parametrize_marks(
-    pytester: Pytester, mark: Optional[str]
+    pytester: Pytester, mark: str | None
 ) -> None:
     if mark is not None:
         pytester.makeini(
-            """
+            f"""
         [pytest]
-        {}={}
-        """.format(
-                EMPTY_PARAMETERSET_OPTION, mark
-            )
+        {EMPTY_PARAMETERSET_OPTION}={mark}
+        """
         )
 
     config = pytester.parseconfig()
-    from _pytest.mark import pytest_configure, get_empty_parameterset_mark
+    from _pytest.mark import get_empty_parameterset_mark
+    from _pytest.mark import pytest_configure
 
     pytest_configure(config)
     result_mark = get_empty_parameterset_mark(config, ["a"], all)
@@ -969,16 +1009,15 @@ def test_parameterset_for_parametrize_marks(
 
 def test_parameterset_for_fail_at_collect(pytester: Pytester) -> None:
     pytester.makeini(
-        """
+        f"""
     [pytest]
-    {}=fail_at_collect
-    """.format(
-            EMPTY_PARAMETERSET_OPTION
-        )
+    {EMPTY_PARAMETERSET_OPTION}=fail_at_collect
+    """
     )
 
     config = pytester.parseconfig()
-    from _pytest.mark import pytest_configure, get_empty_parameterset_mark
+    from _pytest.mark import get_empty_parameterset_mark
+    from _pytest.mark import pytest_configure
 
     pytest_configure(config)
 
@@ -1009,6 +1048,32 @@ def test():
     assert result.ret == ExitCode.INTERRUPTED
 
 
+def test_paramset_empty_no_idfunc(
+    pytester: Pytester, monkeypatch: pytest.MonkeyPatch
+) -> None:
+    """An empty parameter set should not call the user provided id function (#13031)."""
+    p1 = pytester.makepyfile(
+        """
+        import pytest
+
+        def idfunc(value):
+            raise ValueError()
+        @pytest.mark.parametrize("param", [], ids=idfunc)
+        def test(param):
+            pass
+        """
+    )
+    result = pytester.runpytest(p1, "-v", "-rs")
+    result.stdout.fnmatch_lines(
+        [
+            "* collected 1 item",
+            "test_paramset_empty_no_idfunc* SKIPPED *",
+            "SKIPPED [1] test_paramset_empty_no_idfunc.py:5: got empty parameter set for (param)",
+            "*= 1 skipped in *",
+        ]
+    )
+
+
 def test_parameterset_for_parametrize_bad_markname(pytester: Pytester) -> None:
     with pytest.raises(pytest.UsageError):
         test_parameterset_for_parametrize_marks(pytester, "bad")
@@ -1105,7 +1170,11 @@ def test_pytest_param_id_requires_string() -> None:
     with pytest.raises(TypeError) as excinfo:
         pytest.param(id=True)  # type: ignore[arg-type]
     (msg,) = excinfo.value.args
-    assert msg == "Expected id to be a string, got <class 'bool'>: True"
+    expected = (
+        "Expected id to be a string or a `pytest.HIDDEN_PARAM` sentinel, "
+        "got <class 'bool'>: True"
+    )
+    assert msg == expected
 
 
 @pytest.mark.parametrize("s", (None, "hello world"))
@@ -1128,3 +1197,97 @@ def test_foo():
     result = pytester.runpytest(foo, "-m", expr)
     result.stderr.fnmatch_lines([expected])
     assert result.ret == ExitCode.USAGE_ERROR
+
+
+def test_mark_mro() -> None:
+    xfail = pytest.mark.xfail
+
+    @xfail("a")
+    class A:
+        pass
+
+    @xfail("b")
+    class B:
+        pass
+
+    @xfail("c")
+    class C(A, B):
+        pass
+
+    from _pytest.mark.structures import get_unpacked_marks
+
+    all_marks = get_unpacked_marks(C)
+
+    assert all_marks == [xfail("b").mark, xfail("a").mark, xfail("c").mark]
+
+    assert get_unpacked_marks(C, consider_mro=False) == [xfail("c").mark]
+
+
+# @pytest.mark.issue("https://github.com/pytest-dev/pytest/issues/10447")
+def test_mark_fixture_order_mro(pytester: Pytester):
+    """This ensures we walk marks of the mro starting with the base classes
+    the action at a distance fixtures are taken as minimal example from a real project
+
+    """
+    foo = pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture
+        def add_attr1(request):
+            request.instance.attr1 = object()
+
+
+        @pytest.fixture
+        def add_attr2(request):
+            request.instance.attr2 = request.instance.attr1
+
+
+        @pytest.mark.usefixtures('add_attr1')
+        class Parent:
+            pass
+
+
+        @pytest.mark.usefixtures('add_attr2')
+        class TestThings(Parent):
+            def test_attrs(self):
+                assert self.attr1 == self.attr2
+        """
+    )
+    result = pytester.runpytest(foo)
+    result.assert_outcomes(passed=1)
+
+
+def test_mark_parametrize_over_staticmethod(pytester: Pytester) -> None:
+    """Check that applying marks works as intended on classmethods and staticmethods.
+
+    Regression test for #12863.
+    """
+    pytester.makepyfile(
+        """
+        import pytest
+
+        class TestClass:
+            @pytest.mark.parametrize("value", [1, 2])
+            @classmethod
+            def test_classmethod_wrapper(cls, value: int):
+                assert value in [1, 2]
+
+            @classmethod
+            @pytest.mark.parametrize("value", [1, 2])
+            def test_classmethod_wrapper_on_top(cls, value: int):
+                assert value in [1, 2]
+
+            @pytest.mark.parametrize("value", [1, 2])
+            @staticmethod
+            def test_staticmethod_wrapper(value: int):
+                assert value in [1, 2]
+
+            @staticmethod
+            @pytest.mark.parametrize("value", [1, 2])
+            def test_staticmethod_wrapper_on_top(value: int):
+                assert value in [1, 2]
+        """
+    )
+    result = pytester.runpytest()
+    result.assert_outcomes(passed=8)
diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py
index f3643e7b409..884c4b5af88 100644
--- a/testing/test_mark_expression.py
+++ b/testing/test_mark_expression.py
@@ -1,12 +1,17 @@
-from typing import Callable
+from __future__ import annotations
 
-import pytest
+from collections.abc import Callable
+from typing import cast
+
+from _pytest.mark import MarkMatcher
 from _pytest.mark.expression import Expression
+from _pytest.mark.expression import MatcherCall
 from _pytest.mark.expression import ParseError
+import pytest
 
 
 def evaluate(input: str, matcher: Callable[[str], bool]) -> bool:
-    return Expression.compile(input).evaluate(matcher)
+    return Expression.compile(input).evaluate(cast(MatcherCall, matcher))
 
 
 def test_empty_is_false() -> None:
@@ -61,7 +66,7 @@ def test_basic(expr: str, expected: bool) -> None:
         ("not not not not not true", False),
     ),
 )
-def test_syntax_oddeties(expr: str, expected: bool) -> None:
+def test_syntax_oddities(expr: str, expected: bool) -> None:
     matcher = {"true": True, "false": False}.__getitem__
     assert evaluate(expr, matcher) is expected
 
@@ -151,6 +156,8 @@ def test_syntax_errors(expr: str, column: int, message: str) -> None:
         "1234",
         "1234abcd",
         "1234and",
+        "1234or",
+        "1234not",
         "notandor",
         "not_and_or",
         "not[and]or",
@@ -193,3 +200,120 @@ def test_valid_idents(ident: str) -> None:
 def test_invalid_idents(ident: str) -> None:
     with pytest.raises(ParseError):
         evaluate(ident, lambda ident: True)
+
+
+@pytest.mark.parametrize(
+    "expr, expected_error_msg",
+    (
+        ("mark(True=False)", "unexpected reserved python keyword `True`"),
+        ("mark(def=False)", "unexpected reserved python keyword `def`"),
+        ("mark(class=False)", "unexpected reserved python keyword `class`"),
+        ("mark(if=False)", "unexpected reserved python keyword `if`"),
+        ("mark(else=False)", "unexpected reserved python keyword `else`"),
+        ("mark(valid=False, def=1)", "unexpected reserved python keyword `def`"),
+        ("mark(1)", "not a valid python identifier 1"),
+        ("mark(var:=False", "not a valid python identifier var:"),
+        ("mark(1=2)", "not a valid python identifier 1"),
+        ("mark(/=2)", "not a valid python identifier /"),
+        ("mark(var==", "expected identifier; got ="),
+        ("mark(var)", "expected =; got right parenthesis"),
+        ("mark(var=none)", 'unexpected character/s "none"'),
+        ("mark(var=1.1)", 'unexpected character/s "1.1"'),
+        ("mark(var=')", """closing quote "'" is missing"""),
+        ('mark(var=")', 'closing quote """ is missing'),
+        ("""mark(var="')""", 'closing quote """ is missing'),
+        ("""mark(var='")""", """closing quote "'" is missing"""),
+        (
+            r"mark(var='\hugo')",
+            r'escaping with "\\" not supported in marker expression',
+        ),
+        ("mark(empty_list=[])", r'unexpected character/s "\[\]"'),
+        ("'str'", "expected not OR left parenthesis OR identifier; got string literal"),
+    ),
+)
+def test_invalid_kwarg_name_or_value(
+    expr: str, expected_error_msg: str, mark_matcher: MarkMatcher
+) -> None:
+    with pytest.raises(ParseError, match=expected_error_msg):
+        assert evaluate(expr, mark_matcher)
+
+
+@pytest.fixture(scope="session")
+def mark_matcher() -> MarkMatcher:
+    markers = [
+        pytest.mark.number_mark(a=1, b=2, c=3, d=999_999).mark,
+        pytest.mark.builtin_matchers_mark(x=True, y=False, z=None).mark,
+        pytest.mark.str_mark(  # pylint: disable-next=non-ascii-name
+            m="M", space="with space", empty="", aaאבגדcc="aaאבגדcc", אבגד="אבגד"
+        ).mark,
+    ]
+
+    return MarkMatcher.from_markers(markers)
+
+
+@pytest.mark.parametrize(
+    "expr, expected",
+    (
+        # happy cases
+        ("number_mark(a=1)", True),
+        ("number_mark(b=2)", True),
+        ("number_mark(a=1,b=2)", True),
+        ("number_mark(a=1,     b=2)", True),
+        ("number_mark(d=999999)", True),
+        ("number_mark(a   =   1,b= 2,     c = 3)", True),
+        # sad cases
+        ("number_mark(a=6)", False),
+        ("number_mark(b=6)", False),
+        ("number_mark(a=1,b=6)", False),
+        ("number_mark(a=6,b=2)", False),
+        ("number_mark(a   =   1,b= 2,     c = 6)", False),
+        ("number_mark(a='1')", False),
+    ),
+)
+def test_keyword_expressions_with_numbers(
+    expr: str, expected: bool, mark_matcher: MarkMatcher
+) -> None:
+    assert evaluate(expr, mark_matcher) is expected
+
+
+@pytest.mark.parametrize(
+    "expr, expected",
+    (
+        ("builtin_matchers_mark(x=True)", True),
+        ("builtin_matchers_mark(x=False)", False),
+        ("builtin_matchers_mark(y=True)", False),
+        ("builtin_matchers_mark(y=False)", True),
+        ("builtin_matchers_mark(z=None)", True),
+        ("builtin_matchers_mark(z=False)", False),
+        ("builtin_matchers_mark(z=True)", False),
+        ("builtin_matchers_mark(z=0)", False),
+        ("builtin_matchers_mark(z=1)", False),
+    ),
+)
+def test_builtin_matchers_keyword_expressions(
+    expr: str, expected: bool, mark_matcher: MarkMatcher
+) -> None:
+    assert evaluate(expr, mark_matcher) is expected
+
+
+@pytest.mark.parametrize(
+    "expr, expected",
+    (
+        ("str_mark(m='M')", True),
+        ('str_mark(m="M")', True),
+        ("str_mark(aaאבגדcc='aaאבגדcc')", True),
+        ("str_mark(אבגד='אבגד')", True),
+        ("str_mark(space='with space')", True),
+        ("str_mark(empty='')", True),
+        ('str_mark(empty="")', True),
+        ("str_mark(m='wrong')", False),
+        ("str_mark(aaאבגדcc='wrong')", False),
+        ("str_mark(אבגד='wrong')", False),
+        ("str_mark(m='')", False),
+        ('str_mark(m="")', False),
+    ),
+)
+def test_str_keyword_expressions(
+    expr: str, expected: bool, mark_matcher: MarkMatcher
+) -> None:
+    assert evaluate(expr, mark_matcher) is expected
diff --git a/testing/test_meta.py b/testing/test_meta.py
index 9201bd21611..e7d836f7ace 100644
--- a/testing/test_meta.py
+++ b/testing/test_meta.py
@@ -3,16 +3,18 @@
 This ensures all internal packages can be imported without needing the pytest
 namespace being set, which is critical for the initialization of xdist.
 """
+
+from __future__ import annotations
+
 import pkgutil
 import subprocess
 import sys
-from typing import List
 
 import _pytest
 import pytest
 
 
-def _modules() -> List[str]:
+def _modules() -> list[str]:
     pytest_pkg: str = _pytest.__path__  # type: ignore
     return sorted(
         n
diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py
index 95521818021..ad75273d703 100644
--- a/testing/test_monkeypatch.py
+++ b/testing/test_monkeypatch.py
@@ -1,19 +1,20 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Generator
 import os
+from pathlib import Path
 import re
 import sys
 import textwrap
-from pathlib import Path
-from typing import Dict
-from typing import Generator
-from typing import Type
 
-import pytest
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
+import pytest
 
 
 @pytest.fixture
-def mp() -> Generator[MonkeyPatch, None, None]:
+def mp() -> Generator[MonkeyPatch]:
     cwd = os.getcwd()
     sys_path = list(sys.path)
     yield MonkeyPatch()
@@ -50,21 +51,24 @@ class A:
 
 class TestSetattrWithImportPath:
     def test_string_expression(self, monkeypatch: MonkeyPatch) -> None:
-        monkeypatch.setattr("os.path.abspath", lambda x: "hello2")
-        assert os.path.abspath("123") == "hello2"
+        with monkeypatch.context() as mp:
+            mp.setattr("os.path.abspath", lambda x: "hello2")
+            assert os.path.abspath("123") == "hello2"
 
     def test_string_expression_class(self, monkeypatch: MonkeyPatch) -> None:
-        monkeypatch.setattr("_pytest.config.Config", 42)
-        import _pytest
+        with monkeypatch.context() as mp:
+            mp.setattr("_pytest.config.Config", 42)
+            import _pytest
 
-        assert _pytest.config.Config == 42  # type: ignore
+            assert _pytest.config.Config == 42  # type: ignore
 
     def test_unicode_string(self, monkeypatch: MonkeyPatch) -> None:
-        monkeypatch.setattr("_pytest.config.Config", 42)
-        import _pytest
+        with monkeypatch.context() as mp:
+            mp.setattr("_pytest.config.Config", 42)
+            import _pytest
 
-        assert _pytest.config.Config == 42  # type: ignore
-        monkeypatch.delattr("_pytest.config.Config")
+            assert _pytest.config.Config == 42  # type: ignore
+            mp.delattr("_pytest.config.Config")
 
     def test_wrong_target(self, monkeypatch: MonkeyPatch) -> None:
         with pytest.raises(TypeError):
@@ -80,14 +84,16 @@ def test_unknown_attr(self, monkeypatch: MonkeyPatch) -> None:
 
     def test_unknown_attr_non_raising(self, monkeypatch: MonkeyPatch) -> None:
         # https://github.com/pytest-dev/pytest/issues/746
-        monkeypatch.setattr("os.path.qweqwe", 42, raising=False)
-        assert os.path.qweqwe == 42  # type: ignore
+        with monkeypatch.context() as mp:
+            mp.setattr("os.path.qweqwe", 42, raising=False)
+            assert os.path.qweqwe == 42  # type: ignore
 
     def test_delattr(self, monkeypatch: MonkeyPatch) -> None:
-        monkeypatch.delattr("os.path.abspath")
-        assert not hasattr(os.path, "abspath")
-        monkeypatch.undo()
-        assert os.path.abspath
+        with monkeypatch.context() as mp:
+            mp.delattr("os.path.abspath")
+            assert not hasattr(os.path, "abspath")
+            mp.undo()
+            assert os.path.abspath  # type:ignore[truthy-function]
 
 
 def test_delattr() -> None:
@@ -129,7 +135,7 @@ def test_setitem() -> None:
 
 
 def test_setitem_deleted_meanwhile() -> None:
-    d: Dict[str, object] = {}
+    d: dict[str, object] = {}
     monkeypatch = MonkeyPatch()
     monkeypatch.setitem(d, "x", 2)
     del d["x"]
@@ -154,7 +160,7 @@ def test_setenv_deleted_meanwhile(before: bool) -> None:
 
 
 def test_delitem() -> None:
-    d: Dict[str, object] = {"x": 1}
+    d: dict[str, object] = {"x": 1}
     monkeypatch = MonkeyPatch()
     monkeypatch.delitem(d, "x")
     assert "x" not in d
@@ -319,7 +325,8 @@ def test_importerror(pytester: Pytester) -> None:
 
         x = 1
     """
-        )
+        ),
+        encoding="utf-8",
     )
     pytester.path.joinpath("test_importerror.py").write_text(
         textwrap.dedent(
@@ -327,7 +334,8 @@ def test_importerror(pytester: Pytester) -> None:
         def test_importerror(monkeypatch):
             monkeypatch.setattr('package.a.x', 2)
     """
-        )
+        ),
+        encoding="utf-8",
     )
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
@@ -352,7 +360,7 @@ class SampleInherit(Sample):
     [Sample, SampleInherit],
     ids=["new", "new-inherit"],
 )
-def test_issue156_undo_staticmethod(Sample: Type[Sample]) -> None:
+def test_issue156_undo_staticmethod(Sample: type[Sample]) -> None:
     monkeypatch = MonkeyPatch()
 
     monkeypatch.setattr(Sample, "hello", None)
@@ -407,7 +415,7 @@ def test_context() -> None:
     with monkeypatch.context() as m:
         m.setattr(functools, "partial", 3)
         assert not inspect.isclass(functools.partial)
-    assert inspect.isclass(functools.partial)
+    assert inspect.isclass(functools.partial)  # type:ignore[unreachable]
 
 
 def test_context_classmethod() -> None:
@@ -420,6 +428,7 @@ class A:
     assert A.x == 1
 
 
+@pytest.mark.filterwarnings(r"ignore:.*\bpkg_resources\b:DeprecationWarning")
 def test_syspath_prepend_with_namespace_packages(
     pytester: Pytester, monkeypatch: MonkeyPatch
 ) -> None:
@@ -428,11 +437,13 @@ def test_syspath_prepend_with_namespace_packages(
         ns = d.joinpath("ns_pkg")
         ns.mkdir()
         ns.joinpath("__init__.py").write_text(
-            "__import__('pkg_resources').declare_namespace(__name__)"
+            "__import__('pkg_resources').declare_namespace(__name__)", encoding="utf-8"
         )
         lib = ns.joinpath(dirname)
         lib.mkdir()
-        lib.joinpath("__init__.py").write_text("def check(): return %r" % dirname)
+        lib.joinpath("__init__.py").write_text(
+            f"def check(): return {dirname!r}", encoding="utf-8"
+        )
 
     monkeypatch.syspath_prepend("hello")
     import ns_pkg.hello
@@ -451,5 +462,5 @@ def test_syspath_prepend_with_namespace_packages(
     # Should invalidate caches via importlib.invalidate_caches.
     modules_tmpdir = pytester.mkdir("modules_tmpdir")
     monkeypatch.syspath_prepend(str(modules_tmpdir))
-    modules_tmpdir.joinpath("main_app.py").write_text("app = True")
+    modules_tmpdir.joinpath("main_app.py").write_text("app = True", encoding="utf-8")
     from main_app import app  # noqa: F401
diff --git a/testing/test_nodes.py b/testing/test_nodes.py
index c8afe0252be..f039acf243b 100644
--- a/testing/test_nodes.py
+++ b/testing/test_nodes.py
@@ -1,37 +1,17 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from pathlib import Path
+import re
 from typing import cast
-from typing import List
-from typing import Type
+import warnings
 
-import pytest
 from _pytest import nodes
 from _pytest.compat import legacy_path
 from _pytest.outcomes import OutcomeException
 from _pytest.pytester import Pytester
 from _pytest.warning_types import PytestWarning
-
-
-@pytest.mark.parametrize(
-    ("nodeid", "expected"),
-    (
-        ("", [""]),
-        ("a", ["", "a"]),
-        ("aa/b", ["", "aa", "aa/b"]),
-        ("a/b/c", ["", "a", "a/b", "a/b/c"]),
-        ("a/bbb/c::D", ["", "a", "a/bbb", "a/bbb/c", "a/bbb/c::D"]),
-        ("a/b/c::D::eee", ["", "a", "a/b", "a/b/c", "a/b/c::D", "a/b/c::D::eee"]),
-        ("::xx", ["", "::xx"]),
-        # / only considered until first ::
-        ("a/b/c::D/d::e", ["", "a", "a/b", "a/b/c", "a/b/c::D/d", "a/b/c::D/d::e"]),
-        # : alone is not a separator.
-        ("a/b::D:e:f::g", ["", "a", "a/b", "a/b::D:e:f", "a/b::D:e:f::g"]),
-        # / not considered if a part of a test name
-        ("a/b::c/d::e[/test]", ["", "a", "a/b", "a/b::c/d", "a/b::c/d::e[/test]"]),
-    ),
-)
-def test_iterparentnodeids(nodeid: str, expected: List[str]) -> None:
-    result = list(nodes.iterparentnodeids(nodeid))
-    assert result == expected
+import pytest
 
 
 def test_node_from_parent_disallowed_arguments() -> None:
@@ -58,37 +38,43 @@ def test_subclassing_both_item_and_collector_deprecated(
     request, tmp_path: Path
 ) -> None:
     """
-    Verifies we warn on diamond inheritance
-    as well as correctly managing legacy inheritance ctors with missing args
-    as found in plugins
+    Verifies we warn on diamond inheritance as well as correctly managing legacy
+    inheritance constructors with missing args as found in plugins.
     """
-
-    with pytest.warns(
-        PytestWarning,
-        match=(
-            "(?m)SoWrong is an Item subclass and should not be a collector, however its bases File are collectors.\n"
-            "Please split the Collectors and the Item into separate node types.\n.*"
-        ),
-    ):
+    # We do not expect any warnings messages to issued during class definition.
+    with warnings.catch_warnings():
+        warnings.simplefilter("error")
 
         class SoWrong(nodes.Item, nodes.File):
             def __init__(self, fspath, parent):
                 """Legacy ctor with legacy call # don't wana see"""
                 super().__init__(fspath, parent)
 
-    with pytest.warns(
-        PytestWarning, match=".*SoWrong.* not using a cooperative constructor.*"
-    ):
+            def collect(self):
+                raise NotImplementedError()
+
+            def runtest(self):
+                raise NotImplementedError()
+
+    with pytest.warns(PytestWarning) as rec:
         SoWrong.from_parent(
             request.session, fspath=legacy_path(tmp_path / "broken.txt")
         )
+    messages = [str(x.message) for x in rec]
+    assert any(
+        re.search(".*SoWrong.* not using a cooperative constructor.*", x)
+        for x in messages
+    )
+    assert any(
+        re.search("(?m)SoWrong .* should not be a collector", x) for x in messages
+    )
 
 
 @pytest.mark.parametrize(
     "warn_type, msg", [(DeprecationWarning, "deprecated"), (PytestWarning, "pytest")]
 )
 def test_node_warn_is_no_longer_only_pytest_warnings(
-    pytester: Pytester, warn_type: Type[Warning], msg: str
+    pytester: Pytester, warn_type: type[Warning], msg: str
 ) -> None:
     items = pytester.getitems(
         """
diff --git a/testing/test_nose.py b/testing/test_nose.py
deleted file mode 100644
index edca0f4463e..00000000000
--- a/testing/test_nose.py
+++ /dev/null
@@ -1,479 +0,0 @@
-import pytest
-from _pytest.pytester import Pytester
-
-
-def setup_module(mod):
-    mod.nose = pytest.importorskip("nose")
-
-
-def test_nose_setup(pytester: Pytester) -> None:
-    p = pytester.makepyfile(
-        """
-        values = []
-        from nose.tools import with_setup
-
-        @with_setup(lambda: values.append(1), lambda: values.append(2))
-        def test_hello():
-            assert values == [1]
-
-        def test_world():
-            assert values == [1,2]
-
-        test_hello.setup = lambda: values.append(1)
-        test_hello.teardown = lambda: values.append(2)
-    """
-    )
-    result = pytester.runpytest(p, "-p", "nose")
-    result.assert_outcomes(passed=2)
-
-
-def test_setup_func_with_setup_decorator() -> None:
-    from _pytest.nose import call_optional
-
-    values = []
-
-    class A:
-        @pytest.fixture(autouse=True)
-        def f(self):
-            values.append(1)
-
-    call_optional(A(), "f")
-    assert not values
-
-
-def test_setup_func_not_callable() -> None:
-    from _pytest.nose import call_optional
-
-    class A:
-        f = 1
-
-    call_optional(A(), "f")
-
-
-def test_nose_setup_func(pytester: Pytester) -> None:
-    p = pytester.makepyfile(
-        """
-        from nose.tools import with_setup
-
-        values = []
-
-        def my_setup():
-            a = 1
-            values.append(a)
-
-        def my_teardown():
-            b = 2
-            values.append(b)
-
-        @with_setup(my_setup, my_teardown)
-        def test_hello():
-            print(values)
-            assert values == [1]
-
-        def test_world():
-            print(values)
-            assert values == [1,2]
-
-    """
-    )
-    result = pytester.runpytest(p, "-p", "nose")
-    result.assert_outcomes(passed=2)
-
-
-def test_nose_setup_func_failure(pytester: Pytester) -> None:
-    p = pytester.makepyfile(
-        """
-        from nose.tools import with_setup
-
-        values = []
-        my_setup = lambda x: 1
-        my_teardown = lambda x: 2
-
-        @with_setup(my_setup, my_teardown)
-        def test_hello():
-            print(values)
-            assert values == [1]
-
-        def test_world():
-            print(values)
-            assert values == [1,2]
-
-    """
-    )
-    result = pytester.runpytest(p, "-p", "nose")
-    result.stdout.fnmatch_lines(["*TypeError: <lambda>()*"])
-
-
-def test_nose_setup_func_failure_2(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        values = []
-
-        my_setup = 1
-        my_teardown = 2
-
-        def test_hello():
-            assert values == []
-
-        test_hello.setup = my_setup
-        test_hello.teardown = my_teardown
-    """
-    )
-    reprec = pytester.inline_run()
-    reprec.assertoutcome(passed=1)
-
-
-def test_nose_setup_partial(pytester: Pytester) -> None:
-    pytest.importorskip("functools")
-    p = pytester.makepyfile(
-        """
-        from functools import partial
-
-        values = []
-
-        def my_setup(x):
-            a = x
-            values.append(a)
-
-        def my_teardown(x):
-            b = x
-            values.append(b)
-
-        my_setup_partial = partial(my_setup, 1)
-        my_teardown_partial = partial(my_teardown, 2)
-
-        def test_hello():
-            print(values)
-            assert values == [1]
-
-        def test_world():
-            print(values)
-            assert values == [1,2]
-
-        test_hello.setup = my_setup_partial
-        test_hello.teardown = my_teardown_partial
-    """
-    )
-    result = pytester.runpytest(p, "-p", "nose")
-    result.stdout.fnmatch_lines(["*2 passed*"])
-
-
-def test_module_level_setup(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        from nose.tools import with_setup
-        items = {}
-
-        def setup():
-            items.setdefault("setup", []).append("up")
-
-        def teardown():
-            items.setdefault("setup", []).append("down")
-
-        def setup2():
-            items.setdefault("setup2", []).append("up")
-
-        def teardown2():
-            items.setdefault("setup2", []).append("down")
-
-        def test_setup_module_setup():
-            assert items["setup"] == ["up"]
-
-        def test_setup_module_setup_again():
-            assert items["setup"] == ["up"]
-
-        @with_setup(setup2, teardown2)
-        def test_local_setup():
-            assert items["setup"] == ["up"]
-            assert items["setup2"] == ["up"]
-
-        @with_setup(setup2, teardown2)
-        def test_local_setup_again():
-            assert items["setup"] == ["up"]
-            assert items["setup2"] == ["up", "down", "up"]
-    """
-    )
-    result = pytester.runpytest("-p", "nose")
-    result.stdout.fnmatch_lines(["*4 passed*"])
-
-
-def test_nose_style_setup_teardown(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        values = []
-
-        def setup_module():
-            values.append(1)
-
-        def teardown_module():
-            del values[0]
-
-        def test_hello():
-            assert values == [1]
-
-        def test_world():
-            assert values == [1]
-        """
-    )
-    result = pytester.runpytest("-p", "nose")
-    result.stdout.fnmatch_lines(["*2 passed*"])
-
-
-def test_fixtures_nose_setup_issue8394(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        def setup_module():
-            pass
-
-        def teardown_module():
-            pass
-
-        def setup_function(func):
-            pass
-
-        def teardown_function(func):
-            pass
-
-        def test_world():
-            pass
-
-        class Test(object):
-            def setup_class(cls):
-                pass
-
-            def teardown_class(cls):
-                pass
-
-            def setup_method(self, meth):
-                pass
-
-            def teardown_method(self, meth):
-                pass
-
-            def test_method(self): pass
-        """
-    )
-    match = "*no docstring available*"
-    result = pytester.runpytest("--fixtures")
-    assert result.ret == 0
-    result.stdout.no_fnmatch_line(match)
-
-    result = pytester.runpytest("--fixtures", "-v")
-    assert result.ret == 0
-    result.stdout.fnmatch_lines([match, match, match, match])
-
-
-def test_nose_setup_ordering(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        def setup_module(mod):
-            mod.visited = True
-
-        class TestClass(object):
-            def setup(self):
-                assert visited
-                self.visited_cls = True
-            def test_first(self):
-                assert visited
-                assert self.visited_cls
-        """
-    )
-    result = pytester.runpytest()
-    result.stdout.fnmatch_lines(["*1 passed*"])
-
-
-def test_apiwrapper_problem_issue260(pytester: Pytester) -> None:
-    # this would end up trying a call an optional teardown on the class
-    # for plain unittests we don't want nose behaviour
-    pytester.makepyfile(
-        """
-        import unittest
-        class TestCase(unittest.TestCase):
-            def setup(self):
-                #should not be called in unittest testcases
-                assert 0, 'setup'
-            def teardown(self):
-                #should not be called in unittest testcases
-                assert 0, 'teardown'
-            def setUp(self):
-                print('setup')
-            def tearDown(self):
-                print('teardown')
-            def test_fun(self):
-                pass
-        """
-    )
-    result = pytester.runpytest()
-    result.assert_outcomes(passed=1)
-
-
-def test_setup_teardown_linking_issue265(pytester: Pytester) -> None:
-    # we accidentally didn't integrate nose setupstate with normal setupstate
-    # this test ensures that won't happen again
-    pytester.makepyfile(
-        '''
-        import pytest
-
-        class TestGeneric(object):
-            def test_nothing(self):
-                """Tests the API of the implementation (for generic and specialized)."""
-
-        @pytest.mark.skipif("True", reason=
-                    "Skip tests to check if teardown is skipped as well.")
-        class TestSkipTeardown(TestGeneric):
-
-            def setup(self):
-                """Sets up my specialized implementation for $COOL_PLATFORM."""
-                raise Exception("should not call setup for skipped tests")
-
-            def teardown(self):
-                """Undoes the setup."""
-                raise Exception("should not call teardown for skipped tests")
-        '''
-    )
-    reprec = pytester.runpytest()
-    reprec.assert_outcomes(passed=1, skipped=1)
-
-
-def test_SkipTest_during_collection(pytester: Pytester) -> None:
-    p = pytester.makepyfile(
-        """
-        import nose
-        raise nose.SkipTest("during collection")
-        def test_failing():
-            assert False
-        """
-    )
-    result = pytester.runpytest(p)
-    result.assert_outcomes(skipped=1, warnings=1)
-
-
-def test_SkipTest_in_test(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        import nose
-
-        def test_skipping():
-            raise nose.SkipTest("in test")
-        """
-    )
-    reprec = pytester.inline_run()
-    reprec.assertoutcome(skipped=1)
-
-
-def test_istest_function_decorator(pytester: Pytester) -> None:
-    p = pytester.makepyfile(
-        """
-        import nose.tools
-        @nose.tools.istest
-        def not_test_prefix():
-            pass
-        """
-    )
-    result = pytester.runpytest(p)
-    result.assert_outcomes(passed=1)
-
-
-def test_nottest_function_decorator(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        import nose.tools
-        @nose.tools.nottest
-        def test_prefix():
-            pass
-        """
-    )
-    reprec = pytester.inline_run()
-    assert not reprec.getfailedcollections()
-    calls = reprec.getreports("pytest_runtest_logreport")
-    assert not calls
-
-
-def test_istest_class_decorator(pytester: Pytester) -> None:
-    p = pytester.makepyfile(
-        """
-        import nose.tools
-        @nose.tools.istest
-        class NotTestPrefix(object):
-            def test_method(self):
-                pass
-        """
-    )
-    result = pytester.runpytest(p)
-    result.assert_outcomes(passed=1)
-
-
-def test_nottest_class_decorator(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        import nose.tools
-        @nose.tools.nottest
-        class TestPrefix(object):
-            def test_method(self):
-                pass
-        """
-    )
-    reprec = pytester.inline_run()
-    assert not reprec.getfailedcollections()
-    calls = reprec.getreports("pytest_runtest_logreport")
-    assert not calls
-
-
-def test_skip_test_with_unicode(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """\
-        import unittest
-        class TestClass():
-            def test_io(self):
-                raise unittest.SkipTest('😊')
-        """
-    )
-    result = pytester.runpytest()
-    result.stdout.fnmatch_lines(["* 1 skipped *"])
-
-
-def test_raises(pytester: Pytester) -> None:
-    pytester.makepyfile(
-        """
-        from nose.tools import raises
-
-        @raises(RuntimeError)
-        def test_raises_runtimeerror():
-            raise RuntimeError
-
-        @raises(Exception)
-        def test_raises_baseexception_not_caught():
-            raise BaseException
-
-        @raises(BaseException)
-        def test_raises_baseexception_caught():
-            raise BaseException
-        """
-    )
-    result = pytester.runpytest("-vv")
-    result.stdout.fnmatch_lines(
-        [
-            "test_raises.py::test_raises_runtimeerror PASSED*",
-            "test_raises.py::test_raises_baseexception_not_caught FAILED*",
-            "test_raises.py::test_raises_baseexception_caught PASSED*",
-            "*= FAILURES =*",
-            "*_ test_raises_baseexception_not_caught _*",
-            "",
-            "arg = (), kw = {}",
-            "",
-            "    def newfunc(*arg, **kw):",
-            "        try:",
-            ">           func(*arg, **kw)",
-            "",
-            "*/nose/*: ",
-            "_ _ *",
-            "",
-            "    @raises(Exception)",
-            "    def test_raises_baseexception_not_caught():",
-            ">       raise BaseException",
-            "E       BaseException",
-            "",
-            "test_raises.py:9: BaseException",
-            "* 1 failed, 2 passed *",
-        ]
-    )
diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py
index 28529d04378..14e2b5f69fb 100644
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -1,15 +1,19 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import argparse
+import locale
 import os
+from pathlib import Path
 import shlex
 import subprocess
 import sys
-from pathlib import Path
 
-import pytest
 from _pytest.config import argparsing as parseopt
 from _pytest.config.exceptions import UsageError
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
+import pytest
 
 
 @pytest.fixture
@@ -53,9 +57,6 @@ def test_argument_type(self) -> None:
         assert argument.type is str
         argument = parseopt.Argument("-t", dest="abc", type=float)
         assert argument.type is float
-        with pytest.warns(DeprecationWarning):
-            with pytest.raises(KeyError):
-                argument = parseopt.Argument("-t", dest="abc", type="choice")
         argument = parseopt.Argument(
             "-t", dest="abc", type=str, choices=["red", "blue"]
         )
@@ -126,6 +127,17 @@ def test_parse2(self, parser: parseopt.Parser) -> None:
         args = parser.parse([Path(".")])
         assert getattr(args, parseopt.FILE_OR_DIR)[0] == "."
 
+    # Warning ignore because of:
+    # https://github.com/python/cpython/issues/85308
+    # Can be removed once Python<3.12 support is dropped.
+    @pytest.mark.filterwarnings("ignore:'encoding' argument not specified")
+    def test_parse_from_file(self, parser: parseopt.Parser, tmp_path: Path) -> None:
+        tests = [".", "some.py::Test::test_method[param0]", "other/test_file.py"]
+        args_file = tmp_path / "tests.txt"
+        args_file.write_text("\n".join(tests), encoding="utf-8")
+        args = parser.parse([f"@{args_file.absolute()}"])
+        assert getattr(args, parseopt.FILE_OR_DIR) == tests
+
     def test_parse_known_args(self, parser: parseopt.Parser) -> None:
         parser.parse_known_args([Path(".")])
         parser.addoption("--hello", action="store_true")
@@ -289,13 +301,19 @@ def test_multiple_metavar_help(self, parser: parseopt.Parser) -> None:
 
 
 def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
+    if sys.version_info >= (3, 11):
+        # New in Python 3.11, ignores utf-8 mode
+        encoding = locale.getencoding()
+    else:
+        encoding = locale.getpreferredencoding(False)
     try:
         bash_version = subprocess.run(
             ["bash", "--version"],
             stdout=subprocess.PIPE,
             stderr=subprocess.DEVNULL,
             check=True,
-            universal_newlines=True,
+            text=True,
+            encoding=encoding,
         ).stdout
     except (OSError, subprocess.CalledProcessError):
         pytest.skip("bash is not available")
@@ -305,14 +323,12 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
 
     script = str(pytester.path.joinpath("test_argcomplete"))
 
-    with open(str(script), "w") as fp:
+    with open(str(script), "w", encoding="utf-8") as fp:
         # redirect output from argcomplete to stdin and stderr is not trivial
         # http://stackoverflow.com/q/12589419/1307905
         # so we use bash
         fp.write(
-            'COMP_WORDBREAKS="$COMP_WORDBREAKS" {} -m pytest 8>&1 9>&2'.format(
-                shlex.quote(sys.executable)
-            )
+            f'COMP_WORDBREAKS="$COMP_WORDBREAKS" {shlex.quote(sys.executable)} -m pytest 8>&1 9>&2'
         )
     # alternative would be extended Pytester.{run(),_run(),popen()} to be able
     # to handle a keyword argument env that replaces os.environ in popen or
@@ -330,9 +346,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
         pytest.skip("argcomplete not available")
     elif not result.stdout.str():
         pytest.skip(
-            "bash provided no output on stdout, argcomplete not available? (stderr={!r})".format(
-                result.stderr.str()
-            )
+            f"bash provided no output on stdout, argcomplete not available? (stderr={result.stderr.str()!r})"
         )
     else:
         result.stdout.fnmatch_lines(["--funcargs", "--fulltrace"])
diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py
index b338519ae17..8fdd60bac75 100644
--- a/testing/test_pastebin.py
+++ b/testing/test_pastebin.py
@@ -1,16 +1,18 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+import email.message
 import io
-from typing import List
-from typing import Union
 
-import pytest
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
+import pytest
 
 
 class TestPasteCapture:
     @pytest.fixture
-    def pastebinlist(self, monkeypatch, request) -> List[Union[str, bytes]]:
-        pastebinlist: List[Union[str, bytes]] = []
+    def pastebinlist(self, monkeypatch, request) -> list[str | bytes]:
+        pastebinlist: list[str | bytes] = []
         plugin = request.config.pluginmanager.getplugin("pastebin")
         monkeypatch.setattr(plugin, "create_new_paste", pastebinlist.append)
         return pastebinlist
@@ -98,7 +100,9 @@ def mocked_urlopen_fail(self, monkeypatch: MonkeyPatch):
 
         def mocked(url, data):
             calls.append((url, data))
-            raise urllib.error.HTTPError(url, 400, "Bad request", {}, io.BytesIO())
+            raise urllib.error.HTTPError(
+                url, 400, "Bad request", email.message.Message(), io.BytesIO()
+            )
 
         monkeypatch.setattr(urllib.request, "urlopen", mocked)
         return calls
@@ -167,7 +171,7 @@ def test_create_new_paste(self, pastebin, mocked_urlopen) -> None:
         assert type(data) is bytes
         lexer = "text"
         assert url == "https://bpa.st"
-        assert "lexer=%s" % lexer in data.decode()
+        assert f"lexer={lexer}" in data.decode()
         assert "code=full-paste-contents" in data.decode()
         assert "expiry=1week" in data.decode()
 
diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py
index 5eb153e847d..65a4117812f 100644
--- a/testing/test_pathlib.py
+++ b/testing/test_pathlib.py
@@ -1,30 +1,65 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Generator
+from collections.abc import Iterator
+from collections.abc import Sequence
+import errno
+import importlib.abc
+import importlib.machinery
 import os.path
+from pathlib import Path
 import pickle
+import shutil
 import sys
-import unittest.mock
-from pathlib import Path
 from textwrap import dedent
 from types import ModuleType
 from typing import Any
-from typing import Generator
+import unittest.mock
 
-import pytest
+from _pytest.config import ExitCode
 from _pytest.monkeypatch import MonkeyPatch
+from _pytest.pathlib import _import_module_using_spec
 from _pytest.pathlib import bestrelpath
 from _pytest.pathlib import commonpath
+from _pytest.pathlib import compute_module_name
+from _pytest.pathlib import CouldNotResolvePathError
 from _pytest.pathlib import ensure_deletable
 from _pytest.pathlib import fnmatch_ex
 from _pytest.pathlib import get_extended_length_path_str
 from _pytest.pathlib import get_lock_path
 from _pytest.pathlib import import_path
+from _pytest.pathlib import ImportMode
 from _pytest.pathlib import ImportPathMismatchError
 from _pytest.pathlib import insert_missing_modules
+from _pytest.pathlib import is_importable
 from _pytest.pathlib import maybe_delete_a_numbered_dir
 from _pytest.pathlib import module_name_from_path
 from _pytest.pathlib import resolve_package_path
+from _pytest.pathlib import resolve_pkg_root_and_module_name
+from _pytest.pathlib import safe_exists
+from _pytest.pathlib import scandir
+from _pytest.pathlib import spec_matches_module_path
 from _pytest.pathlib import symlink_or_skip
 from _pytest.pathlib import visit
+from _pytest.pytester import Pytester
+from _pytest.pytester import RunResult
 from _pytest.tmpdir import TempPathFactory
+import pytest
+
+
+@pytest.fixture(autouse=True)
+def autouse_pytester(pytester: Pytester) -> None:
+    """
+    Fixture to make pytester() being autouse for all tests in this module.
+
+    pytester makes sure to restore sys.path to its previous state, and many tests in this module
+    import modules and change sys.path because of that, so common module names such as "test" or "test.conftest"
+    end up leaking to tests in other modules.
+
+    Note: we might consider extracting the sys.path restoration aspect into its own fixture, and apply it
+    to the entire test suite always.
+    """
 
 
 class TestFNMatcherPort:
@@ -76,6 +111,15 @@ def test_not_matching(self, pattern: str, path: str) -> None:
         assert not fnmatch_ex(pattern, path)
 
 
+@pytest.fixture(params=[True, False])
+def ns_param(request: pytest.FixtureRequest) -> bool:
+    """
+    Simple parametrized fixture for tests which call import_path() with consider_namespace_packages
+    using True and False.
+    """
+    return bool(request.param)
+
+
 class TestImportPath:
     """
 
@@ -85,22 +129,28 @@ class TestImportPath:
     """
 
     @pytest.fixture(scope="session")
-    def path1(self, tmp_path_factory: TempPathFactory) -> Generator[Path, None, None]:
+    def path1(self, tmp_path_factory: TempPathFactory) -> Generator[Path]:
         path = tmp_path_factory.mktemp("path")
         self.setuptestfs(path)
         yield path
         assert path.joinpath("samplefile").exists()
 
+    @pytest.fixture(autouse=True)
+    def preserve_sys(self):
+        with unittest.mock.patch.dict(sys.modules):
+            with unittest.mock.patch.object(sys, "path", list(sys.path)):
+                yield
+
     def setuptestfs(self, path: Path) -> None:
         # print "setting up test fs for", repr(path)
         samplefile = path / "samplefile"
-        samplefile.write_text("samplefile\n")
+        samplefile.write_text("samplefile\n", encoding="utf-8")
 
         execfile = path / "execfile"
-        execfile.write_text("x=42")
+        execfile.write_text("x=42", encoding="utf-8")
 
         execfilepy = path / "execfile.py"
-        execfilepy.write_text("x=42")
+        execfilepy.write_text("x=42", encoding="utf-8")
 
         d = {1: 2, "hello": "world", "answer": 42}
         path.joinpath("samplepickle").write_bytes(pickle.dumps(d, 1))
@@ -114,9 +164,9 @@ def setuptestfs(self, path: Path) -> None:
         otherdir.joinpath("__init__.py").touch()
 
         module_a = otherdir / "a.py"
-        module_a.write_text("from .b import stuff as result\n")
+        module_a.write_text("from .b import stuff as result\n", encoding="utf-8")
         module_b = otherdir / "b.py"
-        module_b.write_text('stuff="got it"\n')
+        module_b.write_text('stuff="got it"\n', encoding="utf-8")
         module_c = otherdir / "c.py"
         module_c.write_text(
             dedent(
@@ -125,7 +175,8 @@ def setuptestfs(self, path: Path) -> None:
             import otherdir.a
             value = otherdir.a.result
         """
-            )
+            ),
+            encoding="utf-8",
         )
         module_d = otherdir / "d.py"
         module_d.write_text(
@@ -135,181 +186,249 @@ def setuptestfs(self, path: Path) -> None:
             from otherdir import a
             value2 = a.result
         """
-            )
+            ),
+            encoding="utf-8",
         )
 
-    def test_smoke_test(self, path1: Path) -> None:
-        obj = import_path(path1 / "execfile.py", root=path1)
-        assert obj.x == 42  # type: ignore[attr-defined]
+    def test_smoke_test(self, path1: Path, ns_param: bool) -> None:
+        obj = import_path(
+            path1 / "execfile.py", root=path1, consider_namespace_packages=ns_param
+        )
+        assert obj.x == 42
         assert obj.__name__ == "execfile"
 
+    def test_import_path_missing_file(self, path1: Path, ns_param: bool) -> None:
+        with pytest.raises(ImportPathMismatchError):
+            import_path(
+                path1 / "sampledir", root=path1, consider_namespace_packages=ns_param
+            )
+
     def test_renamed_dir_creates_mismatch(
-        self, tmp_path: Path, monkeypatch: MonkeyPatch
+        self, tmp_path: Path, monkeypatch: MonkeyPatch, ns_param: bool
     ) -> None:
         tmp_path.joinpath("a").mkdir()
         p = tmp_path.joinpath("a", "test_x123.py")
         p.touch()
-        import_path(p, root=tmp_path)
+        import_path(p, root=tmp_path, consider_namespace_packages=ns_param)
         tmp_path.joinpath("a").rename(tmp_path.joinpath("b"))
         with pytest.raises(ImportPathMismatchError):
-            import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path)
+            import_path(
+                tmp_path.joinpath("b", "test_x123.py"),
+                root=tmp_path,
+                consider_namespace_packages=ns_param,
+            )
 
         # Errors can be ignored.
         monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1")
-        import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path)
+        import_path(
+            tmp_path.joinpath("b", "test_x123.py"),
+            root=tmp_path,
+            consider_namespace_packages=ns_param,
+        )
 
         # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error.
         monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0")
         with pytest.raises(ImportPathMismatchError):
-            import_path(tmp_path.joinpath("b", "test_x123.py"), root=tmp_path)
+            import_path(
+                tmp_path.joinpath("b", "test_x123.py"),
+                root=tmp_path,
+                consider_namespace_packages=ns_param,
+            )
 
-    def test_messy_name(self, tmp_path: Path) -> None:
+    def test_messy_name(self, tmp_path: Path, ns_param: bool) -> None:
         # https://bitbucket.org/hpk42/py-trunk/issue/129
         path = tmp_path / "foo__init__.py"
         path.touch()
-        module = import_path(path, root=tmp_path)
+        module = import_path(path, root=tmp_path, consider_namespace_packages=ns_param)
         assert module.__name__ == "foo__init__"
 
-    def test_dir(self, tmp_path: Path) -> None:
+    def test_dir(self, tmp_path: Path, ns_param: bool) -> None:
         p = tmp_path / "hello_123"
         p.mkdir()
         p_init = p / "__init__.py"
         p_init.touch()
-        m = import_path(p, root=tmp_path)
+        m = import_path(p, root=tmp_path, consider_namespace_packages=ns_param)
         assert m.__name__ == "hello_123"
-        m = import_path(p_init, root=tmp_path)
+        m = import_path(p_init, root=tmp_path, consider_namespace_packages=ns_param)
         assert m.__name__ == "hello_123"
 
-    def test_a(self, path1: Path) -> None:
+    def test_a(self, path1: Path, ns_param: bool) -> None:
         otherdir = path1 / "otherdir"
-        mod = import_path(otherdir / "a.py", root=path1)
-        assert mod.result == "got it"  # type: ignore[attr-defined]
+        mod = import_path(
+            otherdir / "a.py", root=path1, consider_namespace_packages=ns_param
+        )
+        assert mod.result == "got it"
         assert mod.__name__ == "otherdir.a"
 
-    def test_b(self, path1: Path) -> None:
+    def test_b(self, path1: Path, ns_param: bool) -> None:
         otherdir = path1 / "otherdir"
-        mod = import_path(otherdir / "b.py", root=path1)
-        assert mod.stuff == "got it"  # type: ignore[attr-defined]
+        mod = import_path(
+            otherdir / "b.py", root=path1, consider_namespace_packages=ns_param
+        )
+        assert mod.stuff == "got it"
         assert mod.__name__ == "otherdir.b"
 
-    def test_c(self, path1: Path) -> None:
+    def test_c(self, path1: Path, ns_param: bool) -> None:
         otherdir = path1 / "otherdir"
-        mod = import_path(otherdir / "c.py", root=path1)
-        assert mod.value == "got it"  # type: ignore[attr-defined]
+        mod = import_path(
+            otherdir / "c.py", root=path1, consider_namespace_packages=ns_param
+        )
+        assert mod.value == "got it"
 
-    def test_d(self, path1: Path) -> None:
+    def test_d(self, path1: Path, ns_param: bool) -> None:
         otherdir = path1 / "otherdir"
-        mod = import_path(otherdir / "d.py", root=path1)
-        assert mod.value2 == "got it"  # type: ignore[attr-defined]
+        mod = import_path(
+            otherdir / "d.py", root=path1, consider_namespace_packages=ns_param
+        )
+        assert mod.value2 == "got it"
 
-    def test_import_after(self, tmp_path: Path) -> None:
+    def test_import_after(self, tmp_path: Path, ns_param: bool) -> None:
         tmp_path.joinpath("xxxpackage").mkdir()
         tmp_path.joinpath("xxxpackage", "__init__.py").touch()
         mod1path = tmp_path.joinpath("xxxpackage", "module1.py")
         mod1path.touch()
-        mod1 = import_path(mod1path, root=tmp_path)
+        mod1 = import_path(
+            mod1path, root=tmp_path, consider_namespace_packages=ns_param
+        )
         assert mod1.__name__ == "xxxpackage.module1"
         from xxxpackage import module1
 
         assert module1 is mod1
 
     def test_check_filepath_consistency(
-        self, monkeypatch: MonkeyPatch, tmp_path: Path
+        self, monkeypatch: MonkeyPatch, tmp_path: Path, ns_param: bool
     ) -> None:
         name = "pointsback123"
         p = tmp_path.joinpath(name + ".py")
         p.touch()
-        for ending in (".pyc", ".pyo"):
-            mod = ModuleType(name)
-            pseudopath = tmp_path.joinpath(name + ending)
-            pseudopath.touch()
-            mod.__file__ = str(pseudopath)
-            monkeypatch.setitem(sys.modules, name, mod)
-            newmod = import_path(p, root=tmp_path)
-            assert mod == newmod
-        monkeypatch.undo()
+        with monkeypatch.context() as mp:
+            for ending in (".pyc", ".pyo"):
+                mod = ModuleType(name)
+                pseudopath = tmp_path.joinpath(name + ending)
+                pseudopath.touch()
+                mod.__file__ = str(pseudopath)
+                mp.setitem(sys.modules, name, mod)
+                newmod = import_path(
+                    p, root=tmp_path, consider_namespace_packages=ns_param
+                )
+                assert mod == newmod
         mod = ModuleType(name)
         pseudopath = tmp_path.joinpath(name + "123.py")
         pseudopath.touch()
         mod.__file__ = str(pseudopath)
         monkeypatch.setitem(sys.modules, name, mod)
         with pytest.raises(ImportPathMismatchError) as excinfo:
-            import_path(p, root=tmp_path)
+            import_path(p, root=tmp_path, consider_namespace_packages=ns_param)
         modname, modfile, orig = excinfo.value.args
         assert modname == name
         assert modfile == str(pseudopath)
         assert orig == p
         assert issubclass(ImportPathMismatchError, ImportError)
 
-    def test_issue131_on__init__(self, tmp_path: Path) -> None:
-        # __init__.py files may be namespace packages, and thus the
-        # __file__ of an imported module may not be ourselves
-        # see issue
-        tmp_path.joinpath("proja").mkdir()
-        p1 = tmp_path.joinpath("proja", "__init__.py")
-        p1.touch()
-        tmp_path.joinpath("sub", "proja").mkdir(parents=True)
-        p2 = tmp_path.joinpath("sub", "proja", "__init__.py")
-        p2.touch()
-        m1 = import_path(p1, root=tmp_path)
-        m2 = import_path(p2, root=tmp_path)
-        assert m1 == m2
-
-    def test_ensuresyspath_append(self, tmp_path: Path) -> None:
+    def test_ensuresyspath_append(self, tmp_path: Path, ns_param: bool) -> None:
         root1 = tmp_path / "root1"
         root1.mkdir()
         file1 = root1 / "x123.py"
         file1.touch()
         assert str(root1) not in sys.path
-        import_path(file1, mode="append", root=tmp_path)
+        import_path(
+            file1, mode="append", root=tmp_path, consider_namespace_packages=ns_param
+        )
         assert str(root1) == sys.path[-1]
         assert str(root1) not in sys.path[:-1]
 
-    def test_invalid_path(self, tmp_path: Path) -> None:
+    def test_invalid_path(self, tmp_path: Path, ns_param: bool) -> None:
         with pytest.raises(ImportError):
-            import_path(tmp_path / "invalid.py", root=tmp_path)
+            import_path(
+                tmp_path / "invalid.py",
+                root=tmp_path,
+                consider_namespace_packages=ns_param,
+            )
 
     @pytest.fixture
-    def simple_module(self, tmp_path: Path) -> Path:
-        fn = tmp_path / "_src/tests/mymod.py"
+    def simple_module(
+        self, tmp_path: Path, request: pytest.FixtureRequest
+    ) -> Iterator[Path]:
+        name = f"mymod_{request.node.name}"
+        fn = tmp_path / f"_src/tests/{name}.py"
         fn.parent.mkdir(parents=True)
-        fn.write_text("def foo(x): return 40 + x")
-        return fn
-
-    def test_importmode_importlib(self, simple_module: Path, tmp_path: Path) -> None:
+        fn.write_text("def foo(x): return 40 + x", encoding="utf-8")
+        module_name = module_name_from_path(fn, root=tmp_path)
+        yield fn
+        sys.modules.pop(module_name, None)
+
+    def test_importmode_importlib(
+        self,
+        simple_module: Path,
+        tmp_path: Path,
+        request: pytest.FixtureRequest,
+        ns_param: bool,
+    ) -> None:
         """`importlib` mode does not change sys.path."""
-        module = import_path(simple_module, mode="importlib", root=tmp_path)
-        assert module.foo(2) == 42  # type: ignore[attr-defined]
+        module = import_path(
+            simple_module,
+            mode="importlib",
+            root=tmp_path,
+            consider_namespace_packages=ns_param,
+        )
+        assert module.foo(2) == 42
         assert str(simple_module.parent) not in sys.path
         assert module.__name__ in sys.modules
-        assert module.__name__ == "_src.tests.mymod"
+        assert module.__name__ == f"_src.tests.mymod_{request.node.name}"
         assert "_src" in sys.modules
         assert "_src.tests" in sys.modules
 
-    def test_importmode_twice_is_different_module(
-        self, simple_module: Path, tmp_path: Path
+    def test_remembers_previous_imports(
+        self, simple_module: Path, tmp_path: Path, ns_param: bool
     ) -> None:
-        """`importlib` mode always returns a new module."""
-        module1 = import_path(simple_module, mode="importlib", root=tmp_path)
-        module2 = import_path(simple_module, mode="importlib", root=tmp_path)
-        assert module1 is not module2
+        """`importlib` mode called remembers previous module (#10341, #10811)."""
+        module1 = import_path(
+            simple_module,
+            mode="importlib",
+            root=tmp_path,
+            consider_namespace_packages=ns_param,
+        )
+        module2 = import_path(
+            simple_module,
+            mode="importlib",
+            root=tmp_path,
+            consider_namespace_packages=ns_param,
+        )
+        assert module1 is module2
 
     def test_no_meta_path_found(
-        self, simple_module: Path, monkeypatch: MonkeyPatch, tmp_path: Path
+        self,
+        simple_module: Path,
+        monkeypatch: MonkeyPatch,
+        tmp_path: Path,
+        ns_param: bool,
     ) -> None:
         """Even without any meta_path should still import module."""
         monkeypatch.setattr(sys, "meta_path", [])
-        module = import_path(simple_module, mode="importlib", root=tmp_path)
-        assert module.foo(2) == 42  # type: ignore[attr-defined]
+        module = import_path(
+            simple_module,
+            mode="importlib",
+            root=tmp_path,
+            consider_namespace_packages=ns_param,
+        )
+        assert module.foo(2) == 42
 
         # mode='importlib' fails if no spec is found to load the module
         import importlib.util
 
+        # Force module to be re-imported.
+        del sys.modules[module.__name__]
+
         monkeypatch.setattr(
-            importlib.util, "spec_from_file_location", lambda *args: None
+            importlib.util, "spec_from_file_location", lambda *args, **kwargs: None
         )
         with pytest.raises(ImportError):
-            import_path(simple_module, mode="importlib", root=tmp_path)
+            import_path(
+                simple_module,
+                mode="importlib",
+                root=tmp_path,
+                consider_namespace_packages=False,
+            )
 
 
 def test_resolve_package_path(tmp_path: Path) -> None:
@@ -319,18 +438,18 @@ def test_resolve_package_path(tmp_path: Path) -> None:
     (pkg / "subdir").mkdir()
     (pkg / "subdir/__init__.py").touch()
     assert resolve_package_path(pkg) == pkg
-    assert resolve_package_path(pkg.joinpath("subdir", "__init__.py")) == pkg
+    assert resolve_package_path(pkg / "subdir/__init__.py") == pkg
 
 
 def test_package_unimportable(tmp_path: Path) -> None:
     pkg = tmp_path / "pkg1-1"
     pkg.mkdir()
     pkg.joinpath("__init__.py").touch()
-    subdir = pkg.joinpath("subdir")
+    subdir = pkg / "subdir"
     subdir.mkdir()
-    pkg.joinpath("subdir/__init__.py").touch()
+    (pkg / "subdir/__init__.py").touch()
     assert resolve_package_path(subdir) == subdir
-    xyz = subdir.joinpath("xyz.py")
+    xyz = subdir / "xyz.py"
     xyz.touch()
     assert resolve_package_path(xyz) == subdir
     assert not resolve_package_path(pkg)
@@ -437,7 +556,7 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N
     return False, even when they are clearly equal.
     """
     module_path = tmp_path.joinpath("my_module.py")
-    module_path.write_text("def foo(): return 42")
+    module_path.write_text("def foo(): return 42", encoding="utf-8")
     monkeypatch.syspath_prepend(tmp_path)
 
     with monkeypatch.context() as mp:
@@ -445,13 +564,39 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N
         # the paths too. Using a context to narrow the patch as much as possible given
         # this is an important system function.
         mp.setattr(os.path, "samefile", lambda x, y: False)
-        module = import_path(module_path, root=tmp_path)
+        module = import_path(
+            module_path, root=tmp_path, consider_namespace_packages=False
+        )
     assert getattr(module, "foo")() == 42
 
 
+def test_scandir_with_non_existent_directory() -> None:
+    # Test with a directory that does not exist
+    non_existent_dir = "path_to_non_existent_dir"
+    result = scandir(non_existent_dir)
+    # Assert that the result is an empty list
+    assert result == []
+
+
+def test_scandir_handles_os_error() -> None:
+    # Create a mock entry that will raise an OSError when is_file is called
+    mock_entry = unittest.mock.MagicMock()
+    mock_entry.is_file.side_effect = OSError("some permission error")
+    # Mock os.scandir to return an iterator with our mock entry
+    with unittest.mock.patch("os.scandir") as mock_scandir:
+        mock_scandir.return_value.__enter__.return_value = [mock_entry]
+        # Call the scandir function with a path
+        # We expect an OSError to be raised here
+        with pytest.raises(OSError, match="some permission error"):
+            scandir("/fake/path")
+        # Verify that the is_file method was called on the mock entry
+        mock_entry.is_file.assert_called_once()
+
+
 class TestImportLibMode:
-    @pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
-    def test_importmode_importlib_with_dataclass(self, tmp_path: Path) -> None:
+    def test_importmode_importlib_with_dataclass(
+        self, tmp_path: Path, ns_param: bool
+    ) -> None:
         """Ensure that importlib mode works with a module containing dataclasses (#7856)."""
         fn = tmp_path.joinpath("_src/tests/test_dataclass.py")
         fn.parent.mkdir(parents=True)
@@ -464,16 +609,27 @@ def test_importmode_importlib_with_dataclass(self, tmp_path: Path) -> None:
                 class Data:
                     value: str
                 """
-            )
+            ),
+            encoding="utf-8",
         )
 
-        module = import_path(fn, mode="importlib", root=tmp_path)
+        module = import_path(
+            fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
+        )
         Data: Any = getattr(module, "Data")
         data = Data(value="foo")
         assert data.value == "foo"
         assert data.__module__ == "_src.tests.test_dataclass"
 
-    def test_importmode_importlib_with_pickle(self, tmp_path: Path) -> None:
+        # Ensure we do not import the same module again (#11475).
+        module2 = import_path(
+            fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
+        )
+        assert module is module2
+
+    def test_importmode_importlib_with_pickle(
+        self, tmp_path: Path, ns_param: bool
+    ) -> None:
         """Ensure that importlib mode works with pickle (#7859)."""
         fn = tmp_path.joinpath("_src/tests/test_pickle.py")
         fn.parent.mkdir(parents=True)
@@ -489,16 +645,25 @@ def round_trip():
                     s = pickle.dumps(_action)
                     return pickle.loads(s)
                 """
-            )
+            ),
+            encoding="utf-8",
         )
 
-        module = import_path(fn, mode="importlib", root=tmp_path)
+        module = import_path(
+            fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
+        )
         round_trip = getattr(module, "round_trip")
         action = round_trip()
         assert action() == 42
 
+        # Ensure we do not import the same module again (#11475).
+        module2 = import_path(
+            fn, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
+        )
+        assert module is module2
+
     def test_importmode_importlib_with_pickle_separate_modules(
-        self, tmp_path: Path
+        self, tmp_path: Path, ns_param: bool
     ) -> None:
         """
         Ensure that importlib mode works can load pickles that look similar but are
@@ -509,14 +674,15 @@ def test_importmode_importlib_with_pickle_separate_modules(
         fn1.write_text(
             dedent(
                 """
-                import attr
+                import dataclasses
                 import pickle
 
-                @attr.s(auto_attribs=True)
+                @dataclasses.dataclass
                 class Data:
                     x: int = 42
                 """
-            )
+            ),
+            encoding="utf-8",
         )
 
         fn2 = tmp_path.joinpath("_src/m2/tests/test.py")
@@ -524,14 +690,15 @@ class Data:
         fn2.write_text(
             dedent(
                 """
-                import attr
+                import dataclasses
                 import pickle
 
-                @attr.s(auto_attribs=True)
+                @dataclasses.dataclass
                 class Data:
                     x: str = ""
                 """
-            )
+            ),
+            encoding="utf-8",
         )
 
         import pickle
@@ -540,10 +707,14 @@ def round_trip(obj):
             s = pickle.dumps(obj)
             return pickle.loads(s)
 
-        module = import_path(fn1, mode="importlib", root=tmp_path)
+        module = import_path(
+            fn1, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
+        )
         Data1 = getattr(module, "Data")
 
-        module = import_path(fn2, mode="importlib", root=tmp_path)
+        module = import_path(
+            fn2, mode="importlib", root=tmp_path, consider_namespace_packages=ns_param
+        )
         Data2 = getattr(module, "Data")
 
         assert round_trip(Data1(20)) == Data1(20)
@@ -559,16 +730,1028 @@ def test_module_name_from_path(self, tmp_path: Path) -> None:
         result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar"))
         assert result == "home.foo.test_foo"
 
-    def test_insert_missing_modules(self) -> None:
-        modules = {"src.tests.foo": ModuleType("src.tests.foo")}
-        insert_missing_modules(modules, "src.tests.foo")
-        assert sorted(modules) == ["src", "src.tests", "src.tests.foo"]
+        # Importing __init__.py files should return the package as module name.
+        result = module_name_from_path(tmp_path / "src/app/__init__.py", tmp_path)
+        assert result == "src.app"
+
+        # Unless __init__.py file is at the root, in which case we cannot have an empty module name.
+        result = module_name_from_path(tmp_path / "__init__.py", tmp_path)
+        assert result == "__init__"
+
+        # Modules which start with "." are considered relative and will not be imported
+        # unless part of a package, so we replace it with a "_" when generating the fake module name.
+        result = module_name_from_path(tmp_path / ".env/tests/test_foo.py", tmp_path)
+        assert result == "_env.tests.test_foo"
+
+        # We want to avoid generating extra intermediate modules if some directory just happens
+        # to contain a "." in the name.
+        result = module_name_from_path(
+            tmp_path / ".env.310/tests/test_foo.py", tmp_path
+        )
+        assert result == "_env_310.tests.test_foo"
+
+    def test_resolve_pkg_root_and_module_name(
+        self, tmp_path: Path, monkeypatch: MonkeyPatch, pytester: Pytester
+    ) -> None:
+        # Create a directory structure first without __init__.py files.
+        (tmp_path / "src/app/core").mkdir(parents=True)
+        models_py = tmp_path / "src/app/core/models.py"
+        models_py.touch()
+
+        with pytest.raises(CouldNotResolvePathError):
+            _ = resolve_pkg_root_and_module_name(models_py)
+
+        # Create the __init__.py files, it should now resolve to a proper module name.
+        (tmp_path / "src/app/__init__.py").touch()
+        (tmp_path / "src/app/core/__init__.py").touch()
+        assert resolve_pkg_root_and_module_name(
+            models_py, consider_namespace_packages=True
+        ) == (
+            tmp_path / "src",
+            "app.core.models",
+        )
+
+        # If we add tmp_path to sys.path, src becomes a namespace package.
+        monkeypatch.syspath_prepend(tmp_path)
+        validate_namespace_package(pytester, [tmp_path], ["src.app.core.models"])
+
+        assert resolve_pkg_root_and_module_name(
+            models_py, consider_namespace_packages=True
+        ) == (
+            tmp_path,
+            "src.app.core.models",
+        )
+        assert resolve_pkg_root_and_module_name(
+            models_py, consider_namespace_packages=False
+        ) == (
+            tmp_path / "src",
+            "app.core.models",
+        )
+
+    def test_insert_missing_modules(
+        self, monkeypatch: MonkeyPatch, tmp_path: Path
+    ) -> None:
+        monkeypatch.chdir(tmp_path)
+        # Use 'xxx' and 'xxy' as parent names as they are unlikely to exist and
+        # don't end up being imported.
+        modules = {"xxx.tests.foo": ModuleType("xxx.tests.foo")}
+        insert_missing_modules(modules, "xxx.tests.foo")
+        assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"]
 
         mod = ModuleType("mod", doc="My Module")
-        modules = {"src": mod}
-        insert_missing_modules(modules, "src")
-        assert modules == {"src": mod}
+        modules = {"xxy": mod}
+        insert_missing_modules(modules, "xxy")
+        assert modules == {"xxy": mod}
 
         modules = {}
         insert_missing_modules(modules, "")
         assert modules == {}
+
+    @pytest.mark.parametrize("b_is_package", [True, False])
+    @pytest.mark.parametrize("insert_modules", [True, False])
+    def test_import_module_using_spec(
+        self, b_is_package, insert_modules, tmp_path: Path
+    ):
+        """
+        Verify that `_import_module_using_spec` can obtain a spec based on the path, thereby enabling the import.
+        When importing, not only the target module is imported, but also the parent modules are recursively imported.
+        """
+        file_path = tmp_path / "a/b/c/demo.py"
+        file_path.parent.mkdir(parents=True)
+        file_path.write_text("my_name='demo'", encoding="utf-8")
+
+        if b_is_package:
+            (tmp_path / "a/b/__init__.py").write_text(
+                "my_name='b.__init__'", encoding="utf-8"
+            )
+
+        mod = _import_module_using_spec(
+            "a.b.c.demo",
+            file_path,
+            file_path.parent,
+            insert_modules=insert_modules,
+        )
+
+        # target module is imported
+        assert mod is not None
+        assert spec_matches_module_path(mod.__spec__, file_path) is True
+
+        mod_demo = sys.modules["a.b.c.demo"]
+        assert "demo.py" in str(mod_demo)
+        assert mod_demo.my_name == "demo"  # Imported and available for use
+
+        # parent modules are recursively imported.
+        mod_a = sys.modules["a"]
+        mod_b = sys.modules["a.b"]
+        mod_c = sys.modules["a.b.c"]
+
+        assert mod_a.b is mod_b
+        assert mod_a.b.c is mod_c
+        assert mod_a.b.c.demo is mod_demo
+
+        assert "namespace" in str(mod_a).lower()
+        assert "namespace" in str(mod_c).lower()
+
+        # Compatibility package and namespace package.
+        if b_is_package:
+            assert "namespace" not in str(mod_b).lower()
+            assert "__init__.py" in str(mod_b).lower()  # Imported __init__.py
+            assert mod_b.my_name == "b.__init__"  # Imported and available for use
+
+        else:
+            assert "namespace" in str(mod_b).lower()
+            with pytest.raises(AttributeError):  # Not imported __init__.py
+                assert mod_b.my_name
+
+    def test_parent_contains_child_module_attribute(
+        self, monkeypatch: MonkeyPatch, tmp_path: Path
+    ):
+        monkeypatch.chdir(tmp_path)
+        # Use 'xxx' and 'xxy' as parent names as they are unlikely to exist and
+        # don't end up being imported.
+        modules = {"xxx.tests.foo": ModuleType("xxx.tests.foo")}
+        insert_missing_modules(modules, "xxx.tests.foo")
+        assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"]
+        assert modules["xxx"].tests is modules["xxx.tests"]
+        assert modules["xxx.tests"].foo is modules["xxx.tests.foo"]
+
+    def test_importlib_package(
+        self, monkeypatch: MonkeyPatch, tmp_path: Path, ns_param: bool
+    ):
+        """
+        Importing a package using --importmode=importlib should not import the
+        package's __init__.py file more than once (#11306).
+        """
+        monkeypatch.chdir(tmp_path)
+        monkeypatch.syspath_prepend(tmp_path)
+
+        package_name = "importlib_import_package"
+        tmp_path.joinpath(package_name).mkdir()
+        init = tmp_path.joinpath(f"{package_name}/__init__.py")
+        init.write_text(
+            dedent(
+                """
+                from .singleton import Singleton
+
+                instance = Singleton()
+                """
+            ),
+            encoding="ascii",
+        )
+        singleton = tmp_path.joinpath(f"{package_name}/singleton.py")
+        singleton.write_text(
+            dedent(
+                """
+                class Singleton:
+                    INSTANCES = []
+
+                    def __init__(self) -> None:
+                        self.INSTANCES.append(self)
+                        if len(self.INSTANCES) > 1:
+                            raise RuntimeError("Already initialized")
+                """
+            ),
+            encoding="ascii",
+        )
+
+        mod = import_path(
+            init,
+            root=tmp_path,
+            mode=ImportMode.importlib,
+            consider_namespace_packages=ns_param,
+        )
+        assert len(mod.instance.INSTANCES) == 1
+        # Ensure we do not import the same module again (#11475).
+        mod2 = import_path(
+            init,
+            root=tmp_path,
+            mode=ImportMode.importlib,
+            consider_namespace_packages=ns_param,
+        )
+        assert mod is mod2
+
+    def test_importlib_root_is_package(self, pytester: Pytester) -> None:
+        """
+        Regression for importing a `__init__`.py file that is at the root
+        (#11417).
+        """
+        pytester.makepyfile(__init__="")
+        pytester.makepyfile(
+            """
+            def test_my_test():
+                assert True
+            """
+        )
+
+        result = pytester.runpytest("--import-mode=importlib")
+        result.stdout.fnmatch_lines("* 1 passed *")
+
+    @pytest.mark.parametrize("name", ["code", "time", "math"])
+    def test_importlib_same_name_as_stl(
+        self, pytester, ns_param: bool, tmp_path: Path, name: str
+    ):
+        """Import a namespace package with the same name as the standard library (#13026)."""
+        file_path = pytester.path / f"{name}/foo/test_demo.py"
+        file_path.parent.mkdir(parents=True)
+        file_path.write_text(
+            dedent(
+                """
+            def test_demo():
+                pass
+            """
+            ),
+            encoding="utf-8",
+        )
+
+        # unit test
+        __import__(name)  # import standard library
+
+        import_path(  # import user files
+            file_path,
+            mode=ImportMode.importlib,
+            root=pytester.path,
+            consider_namespace_packages=ns_param,
+        )
+
+        # E2E test
+        result = pytester.runpytest("--import-mode=importlib")
+        result.stdout.fnmatch_lines("* 1 passed *")
+
+    def create_installed_doctests_and_tests_dir(
+        self, path: Path, monkeypatch: MonkeyPatch
+    ) -> tuple[Path, Path, Path]:
+        """
+        Create a directory structure where the application code is installed in a virtual environment,
+        and the tests are in an outside ".tests" directory.
+
+        Return the paths to the core module (installed in the virtualenv), and the test modules.
+        """
+        app = path / "src/app"
+        app.mkdir(parents=True)
+        (app / "__init__.py").touch()
+        core_py = app / "core.py"
+        core_py.write_text(
+            dedent(
+                """
+                def foo():
+                    '''
+                    >>> 1 + 1
+                    2
+                    '''
+                """
+            ),
+            encoding="ascii",
+        )
+
+        # Install it into a site-packages directory, and add it to sys.path, mimicking what
+        # happens when installing into a virtualenv.
+        site_packages = path / ".env/lib/site-packages"
+        site_packages.mkdir(parents=True)
+        shutil.copytree(app, site_packages / "app")
+        assert (site_packages / "app/core.py").is_file()
+
+        monkeypatch.syspath_prepend(site_packages)
+
+        # Create the tests files, outside 'src' and the virtualenv.
+        # We use the same test name on purpose, but in different directories, to ensure
+        # this works as advertised.
+        conftest_path1 = path / ".tests/a/conftest.py"
+        conftest_path1.parent.mkdir(parents=True)
+        conftest_path1.write_text(
+            dedent(
+                """
+                import pytest
+                @pytest.fixture
+                def a_fix(): return "a"
+                """
+            ),
+            encoding="ascii",
+        )
+        test_path1 = path / ".tests/a/test_core.py"
+        test_path1.write_text(
+            dedent(
+                """
+                import app.core
+                def test(a_fix):
+                    assert a_fix == "a"
+                """,
+            ),
+            encoding="ascii",
+        )
+
+        conftest_path2 = path / ".tests/b/conftest.py"
+        conftest_path2.parent.mkdir(parents=True)
+        conftest_path2.write_text(
+            dedent(
+                """
+                import pytest
+                @pytest.fixture
+                def b_fix(): return "b"
+                """
+            ),
+            encoding="ascii",
+        )
+
+        test_path2 = path / ".tests/b/test_core.py"
+        test_path2.write_text(
+            dedent(
+                """
+                import app.core
+                def test(b_fix):
+                    assert b_fix == "b"
+                """,
+            ),
+            encoding="ascii",
+        )
+        return (site_packages / "app/core.py"), test_path1, test_path2
+
+    def test_import_using_normal_mechanism_first(
+        self, monkeypatch: MonkeyPatch, pytester: Pytester, ns_param: bool
+    ) -> None:
+        """
+        Test import_path imports from the canonical location when possible first, only
+        falling back to its normal flow when the module being imported is not reachable via sys.path (#11475).
+        """
+        core_py, test_path1, test_path2 = self.create_installed_doctests_and_tests_dir(
+            pytester.path, monkeypatch
+        )
+
+        # core_py is reached from sys.path, so should be imported normally.
+        mod = import_path(
+            core_py,
+            mode="importlib",
+            root=pytester.path,
+            consider_namespace_packages=ns_param,
+        )
+        assert mod.__name__ == "app.core"
+        assert mod.__file__ and Path(mod.__file__) == core_py
+
+        # Ensure we do not import the same module again (#11475).
+        mod2 = import_path(
+            core_py,
+            mode="importlib",
+            root=pytester.path,
+            consider_namespace_packages=ns_param,
+        )
+        assert mod is mod2
+
+        # tests are not reachable from sys.path, so they are imported as a standalone modules.
+        # Instead of '.tests.a.test_core', we import as "_tests.a.test_core" because
+        # importlib considers module names starting with '.' to be local imports.
+        mod = import_path(
+            test_path1,
+            mode="importlib",
+            root=pytester.path,
+            consider_namespace_packages=ns_param,
+        )
+        assert mod.__name__ == "_tests.a.test_core"
+
+        # Ensure we do not import the same module again (#11475).
+        mod2 = import_path(
+            test_path1,
+            mode="importlib",
+            root=pytester.path,
+            consider_namespace_packages=ns_param,
+        )
+        assert mod is mod2
+
+        mod = import_path(
+            test_path2,
+            mode="importlib",
+            root=pytester.path,
+            consider_namespace_packages=ns_param,
+        )
+        assert mod.__name__ == "_tests.b.test_core"
+
+        # Ensure we do not import the same module again (#11475).
+        mod2 = import_path(
+            test_path2,
+            mode="importlib",
+            root=pytester.path,
+            consider_namespace_packages=ns_param,
+        )
+        assert mod is mod2
+
+    def test_import_using_normal_mechanism_first_integration(
+        self, monkeypatch: MonkeyPatch, pytester: Pytester, ns_param: bool
+    ) -> None:
+        """
+        Same test as above, but verify the behavior calling pytest.
+
+        We should not make this call in the same test as above, as the modules have already
+        been imported by separate import_path() calls.
+        """
+        core_py, test_path1, test_path2 = self.create_installed_doctests_and_tests_dir(
+            pytester.path, monkeypatch
+        )
+        result = pytester.runpytest(
+            "--import-mode=importlib",
+            "-o",
+            f"consider_namespace_packages={ns_param}",
+            "--doctest-modules",
+            "--pyargs",
+            "app",
+            "./.tests",
+        )
+        result.stdout.fnmatch_lines(
+            [
+                f"{core_py.relative_to(pytester.path)} . *",
+                f"{test_path1.relative_to(pytester.path)} . *",
+                f"{test_path2.relative_to(pytester.path)} . *",
+                "* 3 passed*",
+            ]
+        )
+
+    def test_import_path_imports_correct_file(
+        self, pytester: Pytester, ns_param: bool
+    ) -> None:
+        """
+        Import the module by the given path, even if other module with the same name
+        is reachable from sys.path.
+        """
+        pytester.syspathinsert()
+        # Create a 'x.py' module reachable from sys.path that raises AssertionError
+        # if imported.
+        x_at_root = pytester.path / "x.py"
+        x_at_root.write_text("raise AssertionError('x at root')", encoding="ascii")
+
+        # Create another x.py module, but in some subdirectories to ensure it is not
+        # accessible from sys.path.
+        x_in_sub_folder = pytester.path / "a/b/x.py"
+        x_in_sub_folder.parent.mkdir(parents=True)
+        x_in_sub_folder.write_text("X = 'a/b/x'", encoding="ascii")
+
+        # Import our x.py module from the subdirectories.
+        # The 'x.py' module from sys.path was not imported for sure because
+        # otherwise we would get an AssertionError.
+        mod = import_path(
+            x_in_sub_folder,
+            mode=ImportMode.importlib,
+            root=pytester.path,
+            consider_namespace_packages=ns_param,
+        )
+        assert mod.__file__ and Path(mod.__file__) == x_in_sub_folder
+        assert mod.X == "a/b/x"
+
+        mod2 = import_path(
+            x_in_sub_folder,
+            mode=ImportMode.importlib,
+            root=pytester.path,
+            consider_namespace_packages=ns_param,
+        )
+        assert mod is mod2
+
+        # Attempt to import root 'x.py'.
+        with pytest.raises(AssertionError, match="x at root"):
+            _ = import_path(
+                x_at_root,
+                mode=ImportMode.importlib,
+                root=pytester.path,
+                consider_namespace_packages=ns_param,
+            )
+
+
+def test_safe_exists(tmp_path: Path) -> None:
+    d = tmp_path.joinpath("some_dir")
+    d.mkdir()
+    assert safe_exists(d) is True
+
+    f = tmp_path.joinpath("some_file")
+    f.touch()
+    assert safe_exists(f) is True
+
+    # Use unittest.mock() as a context manager to have a very narrow
+    # patch lifetime.
+    p = tmp_path.joinpath("some long filename" * 100)
+    with unittest.mock.patch.object(
+        Path,
+        "exists",
+        autospec=True,
+        side_effect=OSError(errno.ENAMETOOLONG, "name too long"),
+    ):
+        assert safe_exists(p) is False
+
+    with unittest.mock.patch.object(
+        Path,
+        "exists",
+        autospec=True,
+        side_effect=ValueError("name too long"),
+    ):
+        assert safe_exists(p) is False
+
+
+def test_import_sets_module_as_attribute(pytester: Pytester) -> None:
+    """Unittest test for #12194."""
+    pytester.path.joinpath("foo/bar/baz").mkdir(parents=True)
+    pytester.path.joinpath("foo/__init__.py").touch()
+    pytester.path.joinpath("foo/bar/__init__.py").touch()
+    pytester.path.joinpath("foo/bar/baz/__init__.py").touch()
+    pytester.syspathinsert()
+
+    # Import foo.bar.baz and ensure parent modules also ended up imported.
+    baz = import_path(
+        pytester.path.joinpath("foo/bar/baz/__init__.py"),
+        mode=ImportMode.importlib,
+        root=pytester.path,
+        consider_namespace_packages=False,
+    )
+    assert baz.__name__ == "foo.bar.baz"
+    foo = sys.modules["foo"]
+    assert foo.__name__ == "foo"
+    bar = sys.modules["foo.bar"]
+    assert bar.__name__ == "foo.bar"
+
+    # Check parent modules have an attribute pointing to their children.
+    assert bar.baz is baz
+    assert foo.bar is bar
+
+    # Ensure we returned the "foo.bar" module cached in sys.modules.
+    bar_2 = import_path(
+        pytester.path.joinpath("foo/bar/__init__.py"),
+        mode=ImportMode.importlib,
+        root=pytester.path,
+        consider_namespace_packages=False,
+    )
+    assert bar_2 is bar
+
+
+def test_import_sets_module_as_attribute_without_init_files(pytester: Pytester) -> None:
+    """Similar to test_import_sets_module_as_attribute, but without __init__.py files."""
+    pytester.path.joinpath("foo/bar").mkdir(parents=True)
+    pytester.path.joinpath("foo/bar/baz.py").touch()
+    pytester.syspathinsert()
+
+    # Import foo.bar.baz and ensure parent modules also ended up imported.
+    baz = import_path(
+        pytester.path.joinpath("foo/bar/baz.py"),
+        mode=ImportMode.importlib,
+        root=pytester.path,
+        consider_namespace_packages=False,
+    )
+    assert baz.__name__ == "foo.bar.baz"
+    foo = sys.modules["foo"]
+    assert foo.__name__ == "foo"
+    bar = sys.modules["foo.bar"]
+    assert bar.__name__ == "foo.bar"
+
+    # Check parent modules have an attribute pointing to their children.
+    assert bar.baz is baz
+    assert foo.bar is bar
+
+    # Ensure we returned the "foo.bar.baz" module cached in sys.modules.
+    baz_2 = import_path(
+        pytester.path.joinpath("foo/bar/baz.py"),
+        mode=ImportMode.importlib,
+        root=pytester.path,
+        consider_namespace_packages=False,
+    )
+    assert baz_2 is baz
+
+
+def test_import_sets_module_as_attribute_regression(pytester: Pytester) -> None:
+    """Regression test for #12194."""
+    pytester.path.joinpath("foo/bar/baz").mkdir(parents=True)
+    pytester.path.joinpath("foo/__init__.py").touch()
+    pytester.path.joinpath("foo/bar/__init__.py").touch()
+    pytester.path.joinpath("foo/bar/baz/__init__.py").touch()
+    f = pytester.makepyfile(
+        """
+        import foo
+        from foo.bar import baz
+        foo.bar.baz
+
+        def test_foo() -> None:
+            pass
+        """
+    )
+
+    pytester.syspathinsert()
+    result = pytester.runpython(f)
+    assert result.ret == 0
+
+    result = pytester.runpytest("--import-mode=importlib", "--doctest-modules")
+    assert result.ret == 0
+
+
+def test_import_submodule_not_namespace(pytester: Pytester) -> None:
+    """
+    Regression test for importing a submodule 'foo.bar' while there is a 'bar' directory
+    reachable from sys.path -- ensuring the top-level module does not end up imported as a namespace
+    package.
+
+    #12194
+    https://github.com/pytest-dev/pytest/pull/12208#issuecomment-2056458432
+    """
+    pytester.syspathinsert()
+    # Create package 'foo' with a submodule 'bar'.
+    pytester.path.joinpath("foo").mkdir()
+    foo_path = pytester.path.joinpath("foo/__init__.py")
+    foo_path.touch()
+    bar_path = pytester.path.joinpath("foo/bar.py")
+    bar_path.touch()
+    # Create top-level directory in `sys.path` with the same name as that submodule.
+    pytester.path.joinpath("bar").mkdir()
+
+    # Import `foo`, then `foo.bar`, and check they were imported from the correct location.
+    foo = import_path(
+        foo_path,
+        mode=ImportMode.importlib,
+        root=pytester.path,
+        consider_namespace_packages=False,
+    )
+    bar = import_path(
+        bar_path,
+        mode=ImportMode.importlib,
+        root=pytester.path,
+        consider_namespace_packages=False,
+    )
+    assert foo.__name__ == "foo"
+    assert bar.__name__ == "foo.bar"
+    assert foo.__file__ is not None
+    assert bar.__file__ is not None
+    assert Path(foo.__file__) == foo_path
+    assert Path(bar.__file__) == bar_path
+
+
+class TestNamespacePackages:
+    """Test import_path support when importing from properly namespace packages."""
+
+    @pytest.fixture(autouse=True)
+    def setup_imports_tracking(self, monkeypatch: MonkeyPatch) -> None:
+        monkeypatch.setattr(sys, "pytest_namespace_packages_test", [], raising=False)
+
+    def setup_directories(
+        self, tmp_path: Path, monkeypatch: MonkeyPatch | None, pytester: Pytester
+    ) -> tuple[Path, Path]:
+        # Use a code to guard against modules being imported more than once.
+        # This is a safeguard in case future changes break this invariant.
+        code = dedent(
+            """
+            import sys
+            imported = getattr(sys, "pytest_namespace_packages_test", [])
+            assert __name__ not in imported, f"{__name__} already imported"
+            imported.append(__name__)
+            sys.pytest_namespace_packages_test = imported
+            """
+        )
+
+        # Set up a namespace package "com.company", containing
+        # two subpackages, "app" and "calc".
+        (tmp_path / "src/dist1/com/company/app/core").mkdir(parents=True)
+        (tmp_path / "src/dist1/com/company/app/__init__.py").write_text(
+            code, encoding="UTF-8"
+        )
+        (tmp_path / "src/dist1/com/company/app/core/__init__.py").write_text(
+            code, encoding="UTF-8"
+        )
+        models_py = tmp_path / "src/dist1/com/company/app/core/models.py"
+        models_py.touch()
+
+        (tmp_path / "src/dist2/com/company/calc/algo").mkdir(parents=True)
+        (tmp_path / "src/dist2/com/company/calc/__init__.py").write_text(
+            code, encoding="UTF-8"
+        )
+        (tmp_path / "src/dist2/com/company/calc/algo/__init__.py").write_text(
+            code, encoding="UTF-8"
+        )
+        algorithms_py = tmp_path / "src/dist2/com/company/calc/algo/algorithms.py"
+        algorithms_py.write_text(code, encoding="UTF-8")
+
+        r = validate_namespace_package(
+            pytester,
+            [tmp_path / "src/dist1", tmp_path / "src/dist2"],
+            ["com.company.app.core.models", "com.company.calc.algo.algorithms"],
+        )
+        assert r.ret == 0
+        if monkeypatch is not None:
+            monkeypatch.syspath_prepend(tmp_path / "src/dist1")
+            monkeypatch.syspath_prepend(tmp_path / "src/dist2")
+        return models_py, algorithms_py
+
+    @pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"])
+    def test_resolve_pkg_root_and_module_name_ns_multiple_levels(
+        self,
+        tmp_path: Path,
+        monkeypatch: MonkeyPatch,
+        pytester: Pytester,
+        import_mode: str,
+    ) -> None:
+        models_py, algorithms_py = self.setup_directories(
+            tmp_path, monkeypatch, pytester
+        )
+
+        pkg_root, module_name = resolve_pkg_root_and_module_name(
+            models_py, consider_namespace_packages=True
+        )
+        assert (pkg_root, module_name) == (
+            tmp_path / "src/dist1",
+            "com.company.app.core.models",
+        )
+
+        mod = import_path(
+            models_py, mode=import_mode, root=tmp_path, consider_namespace_packages=True
+        )
+        assert mod.__name__ == "com.company.app.core.models"
+        assert mod.__file__ == str(models_py)
+
+        # Ensure we do not import the same module again (#11475).
+        mod2 = import_path(
+            models_py, mode=import_mode, root=tmp_path, consider_namespace_packages=True
+        )
+        assert mod is mod2
+
+        pkg_root, module_name = resolve_pkg_root_and_module_name(
+            algorithms_py, consider_namespace_packages=True
+        )
+        assert (pkg_root, module_name) == (
+            tmp_path / "src/dist2",
+            "com.company.calc.algo.algorithms",
+        )
+
+        mod = import_path(
+            algorithms_py,
+            mode=import_mode,
+            root=tmp_path,
+            consider_namespace_packages=True,
+        )
+        assert mod.__name__ == "com.company.calc.algo.algorithms"
+        assert mod.__file__ == str(algorithms_py)
+
+        # Ensure we do not import the same module again (#11475).
+        mod2 = import_path(
+            algorithms_py,
+            mode=import_mode,
+            root=tmp_path,
+            consider_namespace_packages=True,
+        )
+        assert mod is mod2
+
+    def test_ns_multiple_levels_import_rewrite_assertions(
+        self,
+        tmp_path: Path,
+        monkeypatch: MonkeyPatch,
+        pytester: Pytester,
+    ) -> None:
+        """Check assert rewriting with `--import-mode=importlib` (#12659)."""
+        self.setup_directories(tmp_path, monkeypatch, pytester)
+        code = dedent("""
+        def test():
+            assert "four lights" == "five lights"
+        """)
+
+        # A case is in a subdirectory with an `__init__.py` file.
+        test_py = tmp_path / "src/dist2/com/company/calc/algo/test_demo.py"
+        test_py.write_text(code, encoding="UTF-8")
+
+        pkg_root, module_name = resolve_pkg_root_and_module_name(
+            test_py, consider_namespace_packages=True
+        )
+        assert (pkg_root, module_name) == (
+            tmp_path / "src/dist2",
+            "com.company.calc.algo.test_demo",
+        )
+
+        result = pytester.runpytest("--import-mode=importlib", test_py)
+
+        result.stdout.fnmatch_lines(
+            [
+                "E       AssertionError: assert 'four lights' == 'five lights'",
+                "E         *",
+                "E         - five lights*",
+                "E         + four lights",
+            ]
+        )
+
+    def test_ns_multiple_levels_import_error(
+        self,
+        tmp_path: Path,
+        pytester: Pytester,
+    ) -> None:
+        # Trigger condition 1: ns and file with the same name
+        file = pytester.path / "cow/moo/moo.py"
+        file.parent.mkdir(parents=True)
+        file.write_text("data=123", encoding="utf-8")
+
+        # Trigger condition 2: tests are located in ns
+        tests = pytester.path / "cow/moo/test_moo.py"
+
+        tests.write_text(
+            dedent(
+                """
+            from cow.moo.moo import data
+
+            def test_moo():
+                print(data)
+            """
+            ),
+            encoding="utf-8",
+        )
+
+        result = pytester.runpytest("--import-mode=importlib")
+        assert result.ret == ExitCode.OK
+
+    @pytest.mark.parametrize("import_mode", ["prepend", "append", "importlib"])
+    def test_incorrect_namespace_package(
+        self,
+        tmp_path: Path,
+        monkeypatch: MonkeyPatch,
+        pytester: Pytester,
+        import_mode: str,
+    ) -> None:
+        models_py, algorithms_py = self.setup_directories(
+            tmp_path, monkeypatch, pytester
+        )
+        # Namespace packages must not have an __init__.py at its top-level
+        # directory; if it does, it is no longer a namespace package, and we fall back
+        # to importing just the part of the package containing the __init__.py files.
+        (tmp_path / "src/dist1/com/__init__.py").touch()
+
+        # Because of the __init__ file, 'com' is no longer a namespace package:
+        # 'com.company.app' is importable as a normal module.
+        # 'com.company.calc' is no longer importable because 'com' is not a namespace package anymore.
+        r = validate_namespace_package(
+            pytester,
+            [tmp_path / "src/dist1", tmp_path / "src/dist2"],
+            ["com.company.app.core.models", "com.company.calc.algo.algorithms"],
+        )
+        assert r.ret == 1
+        r.stderr.fnmatch_lines("*No module named 'com.company.calc*")
+
+        pkg_root, module_name = resolve_pkg_root_and_module_name(
+            models_py, consider_namespace_packages=True
+        )
+        assert (pkg_root, module_name) == (
+            tmp_path / "src/dist1",
+            "com.company.app.core.models",
+        )
+
+        # dist2/com/company will contain a normal Python package.
+        pkg_root, module_name = resolve_pkg_root_and_module_name(
+            algorithms_py, consider_namespace_packages=True
+        )
+        assert (pkg_root, module_name) == (
+            tmp_path / "src/dist2/com/company",
+            "calc.algo.algorithms",
+        )
+
+    def test_detect_meta_path(
+        self,
+        tmp_path: Path,
+        monkeypatch: MonkeyPatch,
+        pytester: Pytester,
+    ) -> None:
+        """
+        resolve_pkg_root_and_module_name() considers sys.meta_path when importing namespace packages.
+
+        Regression test for #12112.
+        """
+
+        class CustomImporter(importlib.abc.MetaPathFinder):
+            """
+            Imports the module name "com" as a namespace package.
+
+            This ensures our namespace detection considers sys.meta_path, which is important
+            to support all possible ways a module can be imported (for example editable installs).
+            """
+
+            def find_spec(
+                self, name: str, path: Any = None, target: Any = None
+            ) -> importlib.machinery.ModuleSpec | None:
+                if name == "com":
+                    spec = importlib.machinery.ModuleSpec("com", loader=None)
+                    spec.submodule_search_locations = [str(com_root_2), str(com_root_1)]
+                    return spec
+                return None
+
+        # Setup directories without configuring sys.path.
+        models_py, algorithms_py = self.setup_directories(
+            tmp_path, monkeypatch=None, pytester=pytester
+        )
+        com_root_1 = tmp_path / "src/dist1/com"
+        com_root_2 = tmp_path / "src/dist2/com"
+
+        # Because the namespace package is not setup correctly, we cannot resolve it as a namespace package.
+        pkg_root, module_name = resolve_pkg_root_and_module_name(
+            models_py, consider_namespace_packages=True
+        )
+        assert (pkg_root, module_name) == (
+            tmp_path / "src/dist1/com/company",
+            "app.core.models",
+        )
+
+        # Insert our custom importer, which will recognize the "com" directory as a namespace package.
+        new_meta_path = [CustomImporter(), *sys.meta_path]
+        monkeypatch.setattr(sys, "meta_path", new_meta_path)
+
+        # Now we should be able to resolve the path as namespace package.
+        pkg_root, module_name = resolve_pkg_root_and_module_name(
+            models_py, consider_namespace_packages=True
+        )
+        assert (pkg_root, module_name) == (
+            tmp_path / "src/dist1",
+            "com.company.app.core.models",
+        )
+
+    @pytest.mark.parametrize("insert", [True, False])
+    def test_full_ns_packages_without_init_files(
+        self, pytester: Pytester, tmp_path: Path, monkeypatch: MonkeyPatch, insert: bool
+    ) -> None:
+        (tmp_path / "src/dist1/ns/b/app/bar/test").mkdir(parents=True)
+        (tmp_path / "src/dist1/ns/b/app/bar/m.py").touch()
+
+        if insert:
+            # The presence of this __init__.py is not a problem, ns.b.app is still part of the namespace package.
+            (tmp_path / "src/dist1/ns/b/app/__init__.py").touch()
+
+        (tmp_path / "src/dist2/ns/a/core/foo/test").mkdir(parents=True)
+        (tmp_path / "src/dist2/ns/a/core/foo/m.py").touch()
+
+        # Validate the namespace package by importing it in a Python subprocess.
+        r = validate_namespace_package(
+            pytester,
+            [tmp_path / "src/dist1", tmp_path / "src/dist2"],
+            ["ns.b.app.bar.m", "ns.a.core.foo.m"],
+        )
+        assert r.ret == 0
+        monkeypatch.syspath_prepend(tmp_path / "src/dist1")
+        monkeypatch.syspath_prepend(tmp_path / "src/dist2")
+
+        assert resolve_pkg_root_and_module_name(
+            tmp_path / "src/dist1/ns/b/app/bar/m.py", consider_namespace_packages=True
+        ) == (tmp_path / "src/dist1", "ns.b.app.bar.m")
+        assert resolve_pkg_root_and_module_name(
+            tmp_path / "src/dist2/ns/a/core/foo/m.py", consider_namespace_packages=True
+        ) == (tmp_path / "src/dist2", "ns.a.core.foo.m")
+
+
+def test_ns_import_same_name_directory_12592(
+    tmp_path: Path, pytester: Pytester
+) -> None:
+    """Regression for `--import-mode=importlib` with directory parent and child with same name (#12592)."""
+    y_dir = tmp_path / "x/y/y"
+    y_dir.mkdir(parents=True)
+    test_y = tmp_path / "x/y/test_y.py"
+    test_y.write_text("def test(): pass", encoding="UTF-8")
+
+    result = pytester.runpytest("--import-mode=importlib", test_y)
+    assert result.ret == ExitCode.OK
+
+
+def test_is_importable(pytester: Pytester) -> None:
+    pytester.syspathinsert()
+
+    path = pytester.path / "bar/foo.py"
+    path.parent.mkdir()
+    path.touch()
+    assert is_importable("bar.foo", path) is True
+
+    # Ensure that the module that can be imported points to the path we expect.
+    path = pytester.path / "some/other/path/bar/foo.py"
+    path.mkdir(parents=True, exist_ok=True)
+    assert is_importable("bar.foo", path) is False
+
+    # Paths containing "." cannot be imported.
+    path = pytester.path / "bar.x/__init__.py"
+    path.parent.mkdir()
+    path.touch()
+    assert is_importable("bar.x", path) is False
+
+    # Pass starting with "." denote relative imports and cannot be checked using is_importable.
+    path = pytester.path / ".bar.x/__init__.py"
+    path.parent.mkdir()
+    path.touch()
+    assert is_importable(".bar.x", path) is False
+
+
+def test_compute_module_name(tmp_path: Path) -> None:
+    assert compute_module_name(tmp_path, tmp_path) is None
+    assert compute_module_name(Path(), Path()) is None
+
+    assert compute_module_name(tmp_path, tmp_path / "mod.py") == "mod"
+    assert compute_module_name(tmp_path, tmp_path / "src/app/bar") == "src.app.bar"
+    assert compute_module_name(tmp_path, tmp_path / "src/app/bar.py") == "src.app.bar"
+    assert (
+        compute_module_name(tmp_path, tmp_path / "src/app/bar/__init__.py")
+        == "src.app.bar"
+    )
+
+
+def validate_namespace_package(
+    pytester: Pytester, paths: Sequence[Path], modules: Sequence[str]
+) -> RunResult:
+    """
+    Validate that a Python namespace package is set up correctly.
+
+    In a sub interpreter, add 'paths' to sys.path and attempt to import the given modules.
+
+    In this module many tests configure a set of files as a namespace package, this function
+    is used as sanity check that our files are configured correctly from the point of view of Python.
+    """
+    lines = [
+        "import sys",
+        # Configure sys.path.
+        *[f"sys.path.append(r{str(x)!r})" for x in paths],
+        # Imports.
+        *[f"import {x}" for x in modules],
+    ]
+    return pytester.runpython_c("\n".join(lines))
diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py
index 9fe23d17792..db85124bf0d 100644
--- a/testing/test_pluginmanager.py
+++ b/testing/test_pluginmanager.py
@@ -1,10 +1,11 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import os
 import shutil
 import sys
 import types
-from typing import List
 
-import pytest
 from _pytest.config import Config
 from _pytest.config import ExitCode
 from _pytest.config import PytestPluginManager
@@ -13,6 +14,7 @@
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pathlib import import_path
 from _pytest.pytester import Pytester
+import pytest
 
 
 @pytest.fixture
@@ -45,7 +47,10 @@ def pytest_myhook(xyz):
             kwargs=dict(pluginmanager=config.pluginmanager)
         )
         config.pluginmanager._importconftest(
-            conf, importmode="prepend", rootpath=pytester.path
+            conf,
+            importmode="prepend",
+            rootpath=pytester.path,
+            consider_namespace_packages=False,
         )
         # print(config.pluginmanager.get_plugins())
         res = config.hook.pytest_myhook(xyz=10)
@@ -74,7 +79,10 @@ def pytest_addoption(parser):
         """
         )
         config.pluginmanager._importconftest(
-            p, importmode="prepend", rootpath=pytester.path
+            p,
+            importmode="prepend",
+            rootpath=pytester.path,
+            consider_namespace_packages=False,
         )
         assert config.option.test123
 
@@ -98,6 +106,40 @@ def pytest_configure(self):
         config.pluginmanager.register(A())
         assert len(values) == 2
 
+    @pytest.mark.skipif(
+        not sys.platform.startswith("win"),
+        reason="requires a case-insensitive file system",
+    )
+    def test_conftestpath_case_sensitivity(self, pytester: Pytester) -> None:
+        """Unit test for issue #9765."""
+        config = pytester.parseconfig()
+        pytester.makepyfile(**{"tests/conftest.py": ""})
+
+        conftest = pytester.path.joinpath("tests/conftest.py")
+        conftest_upper_case = pytester.path.joinpath("TESTS/conftest.py")
+
+        mod = config.pluginmanager._importconftest(
+            conftest,
+            importmode="prepend",
+            rootpath=pytester.path,
+            consider_namespace_packages=False,
+        )
+        plugin = config.pluginmanager.get_plugin(str(conftest))
+        assert plugin is mod
+
+        mod_uppercase = config.pluginmanager._importconftest(
+            conftest_upper_case,
+            importmode="prepend",
+            rootpath=pytester.path,
+            consider_namespace_packages=False,
+        )
+        plugin_uppercase = config.pluginmanager.get_plugin(str(conftest_upper_case))
+        assert plugin_uppercase is mod_uppercase
+
+        # No str(conftestpath) normalization so conftest should be imported
+        # twice and modules should be different objects
+        assert mod is not mod_uppercase
+
     def test_hook_tracing(self, _config_for_test: Config) -> None:
         pytestpm = _config_for_test.pluginmanager  # fully initialized with plugins
         saveindent = []
@@ -111,7 +153,7 @@ def pytest_plugin_registered(self):
                 saveindent.append(pytestpm.trace.root.indent)
                 raise ValueError()
 
-        values: List[str] = []
+        values: list[str] = []
         pytestpm.trace.root.setwriter(values.append)
         undo = pytestpm.enable_tracing()
         try:
@@ -141,12 +183,18 @@ def test_hook_proxy(self, pytester: Pytester) -> None:
         conftest2 = pytester.path.joinpath("tests/subdir/conftest.py")
 
         config.pluginmanager._importconftest(
-            conftest1, importmode="prepend", rootpath=pytester.path
+            conftest1,
+            importmode="prepend",
+            rootpath=pytester.path,
+            consider_namespace_packages=False,
         )
         ihook_a = session.gethookproxy(pytester.path / "tests")
         assert ihook_a is not None
         config.pluginmanager._importconftest(
-            conftest2, importmode="prepend", rootpath=pytester.path
+            conftest2,
+            importmode="prepend",
+            rootpath=pytester.path,
+            consider_namespace_packages=False,
         )
         ihook_b = session.gethookproxy(pytester.path / "tests")
         assert ihook_a is not ihook_b
@@ -242,8 +290,12 @@ def test_consider_module(
         mod = types.ModuleType("temp")
         mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"]
         pytestpm.consider_module(mod)
-        assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1"
-        assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2"
+        p1 = pytestpm.get_plugin("pytest_p1")
+        assert p1 is not None
+        assert p1.__name__ == "pytest_p1"
+        p2 = pytestpm.get_plugin("pytest_p2")
+        assert p2 is not None
+        assert p2.__name__ == "pytest_p2"
 
     def test_consider_module_import_module(
         self, pytester: Pytester, _config_for_test: Config
@@ -336,6 +388,7 @@ def test_import_plugin_importname(
         len2 = len(pytestpm.get_plugins())
         assert len1 == len2
         plugin1 = pytestpm.get_plugin("pytest_hello")
+        assert plugin1 is not None
         assert plugin1.__name__.endswith("pytest_hello")
         plugin2 = pytestpm.get_plugin("pytest_hello")
         assert plugin2 is plugin1
@@ -347,10 +400,11 @@ def test_import_plugin_dotted_name(
         pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y")
 
         pytester.syspathinsert()
-        pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3")
+        pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3", encoding="utf-8")
         pluginname = "pkg.plug"
         pytestpm.import_plugin(pluginname)
         mod = pytestpm.get_plugin("pkg.plug")
+        assert mod is not None
         assert mod.x == 3
 
     def test_consider_conftest_deps(
@@ -359,13 +413,15 @@ def test_consider_conftest_deps(
         pytestpm: PytestPluginManager,
     ) -> None:
         mod = import_path(
-            pytester.makepyfile("pytest_plugins='xyz'"), root=pytester.path
+            pytester.makepyfile("pytest_plugins='xyz'"),
+            root=pytester.path,
+            consider_namespace_packages=False,
         )
         with pytest.raises(ImportError):
-            pytestpm.consider_conftest(mod)
+            pytestpm.consider_conftest(mod, registration_name="unused")
 
 
-class TestPytestPluginManagerBootstrapming:
+class TestPytestPluginManagerBootstrapping:
     def test_preparse_args(self, pytestpm: PytestPluginManager) -> None:
         pytest.raises(
             ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"])
@@ -391,7 +447,7 @@ def test_plugin_prevent_register(self, pytestpm: PytestPluginManager) -> None:
         assert len(l2) == len(l1)
         assert 42 not in l2
 
-    def test_plugin_prevent_register_unregistered_alredy_registered(
+    def test_plugin_prevent_register_unregistered_already_registered(
         self, pytestpm: PytestPluginManager
     ) -> None:
         pytestpm.register(42, name="abc")
diff --git a/testing/test_pytester.py b/testing/test_pytester.py
index bc6e52aba0e..87714b4708f 100644
--- a/testing/test_pytester.py
+++ b/testing/test_pytester.py
@@ -1,22 +1,22 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import os
 import subprocess
 import sys
 import time
-from pathlib import Path
 from types import ModuleType
-from typing import List
 
-import _pytest.pytester as pytester_mod
-import pytest
 from _pytest.config import ExitCode
 from _pytest.config import PytestPluginManager
 from _pytest.monkeypatch import MonkeyPatch
-from _pytest.pytester import CwdSnapshot
+import _pytest.pytester as pytester_mod
 from _pytest.pytester import HookRecorder
 from _pytest.pytester import LineMatcher
 from _pytest.pytester import Pytester
 from _pytest.pytester import SysModulesSnapshot
 from _pytest.pytester import SysPathsSnapshot
+import pytest
 
 
 def test_make_hook_recorder(pytester: Pytester) -> None:
@@ -222,13 +222,13 @@ def test_inline_run_test_module_not_cleaned_up(self, pytester: Pytester) -> None
         result = pytester.inline_run(str(test_mod))
         assert result.ret == ExitCode.OK
         # rewrite module, now test should fail if module was re-imported
-        test_mod.write_text("def test_foo(): assert False")
+        test_mod.write_text("def test_foo(): assert False", encoding="utf-8")
         result2 = pytester.inline_run(str(test_mod))
         assert result2.ret == ExitCode.TESTS_FAILED
 
     def spy_factory(self):
         class SysModulesSnapshotSpy:
-            instances: List["SysModulesSnapshotSpy"] = []  # noqa: F821
+            instances: list[SysModulesSnapshotSpy] = []
 
             def __init__(self, preserve=None) -> None:
                 SysModulesSnapshotSpy.instances.append(self)
@@ -301,17 +301,6 @@ def test_assert_outcomes_after_pytest_error(pytester: Pytester) -> None:
         result.assert_outcomes(passed=0)
 
 
-def test_cwd_snapshot(pytester: Pytester) -> None:
-    foo = pytester.mkdir("foo")
-    bar = pytester.mkdir("bar")
-    os.chdir(foo)
-    snapshot = CwdSnapshot()
-    os.chdir(bar)
-    assert Path().absolute() == bar
-    snapshot.restore()
-    assert Path().absolute() == foo
-
-
 class TestSysModulesSnapshot:
     key = "my-test-module"
 
@@ -411,7 +400,7 @@ def test_preserve_container(self, monkeypatch: MonkeyPatch, path_type) -> None:
         original_data = list(getattr(sys, path_type))
         original_other = getattr(sys, other_path_type)
         original_other_data = list(original_other)
-        new: List[object] = []
+        new: list[object] = []
         snapshot = SysPathsSnapshot()
         monkeypatch.setattr(sys, path_type, new)
         snapshot.restore()
@@ -618,14 +607,9 @@ def test_linematcher_string_api() -> None:
 
 
 def test_pytest_addopts_before_pytester(request, monkeypatch: MonkeyPatch) -> None:
-    orig = os.environ.get("PYTEST_ADDOPTS", None)
     monkeypatch.setenv("PYTEST_ADDOPTS", "--orig-unused")
-    pytester: Pytester = request.getfixturevalue("pytester")
+    _: Pytester = request.getfixturevalue("pytester")
     assert "PYTEST_ADDOPTS" not in os.environ
-    pytester._finalize()
-    assert os.environ.get("PYTEST_ADDOPTS") == "--orig-unused"
-    monkeypatch.undo()
-    assert os.environ.get("PYTEST_ADDOPTS") == orig
 
 
 def test_run_stdin(pytester: Pytester) -> None:
@@ -722,15 +706,13 @@ def test_spawn_uses_tmphome(pytester: Pytester) -> None:
     pytester._monkeypatch.setenv("CUSTOMENV", "42")
 
     p1 = pytester.makepyfile(
-        """
+        f"""
         import os
 
         def test():
             assert os.environ["HOME"] == {tmphome!r}
             assert os.environ["CUSTOMENV"] == "42"
-        """.format(
-            tmphome=tmphome
-        )
+        """
     )
     child = pytester.spawn_pytest(str(p1))
     out = child.read()
@@ -743,8 +725,8 @@ def test_run_result_repr() -> None:
 
     # known exit code
     r = pytester_mod.RunResult(1, outlines, errlines, duration=0.5)
-    assert (
-        repr(r) == "<RunResult ret=ExitCode.TESTS_FAILED len(stdout.lines)=3"
+    assert repr(r) == (
+        f"<RunResult ret={pytest.ExitCode.TESTS_FAILED!s} len(stdout.lines)=3"
         " len(stderr.lines)=4 duration=0.50s>"
     )
 
@@ -835,6 +817,8 @@ def test_with_warning():
     )
     result = pytester.runpytest()
     result.assert_outcomes(passed=1, warnings=1)
+    # If warnings is not passed, it is not checked at all.
+    result.assert_outcomes(passed=1)
 
 
 def test_pytester_outcomes_deselected(pytester: Pytester) -> None:
@@ -849,3 +833,5 @@ def test_two():
     )
     result = pytester.runpytest("-k", "test_one")
     result.assert_outcomes(passed=1, deselected=1)
+    # If deselected is not passed, it is not checked at all.
+    result.assert_outcomes(passed=1)
diff --git a/testing/test_pythonpath.py b/testing/test_python_path.py
similarity index 69%
rename from testing/test_pythonpath.py
rename to testing/test_python_path.py
index 97c439ce0c3..d12ef96115f 100644
--- a/testing/test_pythonpath.py
+++ b/testing/test_python_path.py
@@ -1,11 +1,11 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import sys
 from textwrap import dedent
-from typing import Generator
-from typing import List
-from typing import Optional
 
-import pytest
 from _pytest.pytester import Pytester
+import pytest
 
 
 @pytest.fixture()
@@ -61,6 +61,27 @@ def test_two_dirs(pytester: Pytester, file_structure) -> None:
     result.assert_outcomes(passed=2)
 
 
+def test_local_plugin(pytester: Pytester, file_structure) -> None:
+    """`pythonpath` kicks early enough to load plugins via -p (#11118)."""
+    localplugin_py = pytester.path / "sub" / "localplugin.py"
+    content = dedent(
+        """
+        def pytest_load_initial_conftests():
+            print("local plugin load")
+
+        def pytest_unconfigure():
+            print("local plugin unconfig")
+        """
+    )
+    localplugin_py.write_text(content, encoding="utf-8")
+
+    pytester.makeini("[pytest]\npythonpath=sub\n")
+    result = pytester.runpytest("-plocalplugin", "-s", "test_foo.py")
+    result.stdout.fnmatch_lines(["local plugin load", "local plugin unconfig"])
+    assert result.ret == 0
+    result.assert_outcomes(passed=1)
+
+
 def test_module_not_found(pytester: Pytester, file_structure) -> None:
     """Without the pythonpath setting, the module should not be found."""
     pytester.makefile(".ini", pytest="[pytest]\n")
@@ -81,27 +102,26 @@ def test_no_ini(pytester: Pytester, file_structure) -> None:
 
 
 def test_clean_up(pytester: Pytester) -> None:
-    """Test that the pythonpath plugin cleans up after itself."""
-    # This is tough to test behaviorly because the cleanup really runs last.
+    """Test that the plugin cleans up after itself."""
+    # This is tough to test behaviorally because the cleanup really runs last.
     # So the test make several implementation assumptions:
     # - Cleanup is done in pytest_unconfigure().
-    # - Not a hookwrapper.
-    # So we can add a hookwrapper ourselves to test what it does.
+    # - Not a hook wrapper.
+    # So we can add a hook wrapper ourselves to test what it does.
     pytester.makefile(".ini", pytest="[pytest]\npythonpath=I_SHALL_BE_REMOVED\n")
     pytester.makepyfile(test_foo="""def test_foo(): pass""")
 
-    before: Optional[List[str]] = None
-    after: Optional[List[str]] = None
+    before: list[str] | None = None
+    after: list[str] | None = None
 
     class Plugin:
-        @pytest.hookimpl(hookwrapper=True, tryfirst=True)
-        def pytest_unconfigure(self) -> Generator[None, None, None]:
-            nonlocal before, after
+        @pytest.hookimpl(tryfirst=True)
+        def pytest_unconfigure(self) -> None:
+            nonlocal before
             before = sys.path.copy()
-            yield
-            after = sys.path.copy()
 
     result = pytester.runpytest_inprocess(plugins=[Plugin()])
+    after = sys.path.copy()
     assert result.ret == 0
 
     assert before is not None
diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py
index d3f218f1660..384f2b66a15 100644
--- a/testing/test_recwarn.py
+++ b/testing/test_recwarn.py
@@ -1,10 +1,13 @@
-import re
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+import sys
 import warnings
-from typing import Optional
 
 import pytest
-from _pytest.pytester import Pytester
-from _pytest.recwarn import WarningsRecorder
+from pytest import ExitCode
+from pytest import Pytester
+from pytest import WarningsRecorder
 
 
 def test_recwarn_stacklevel(recwarn: WarningsRecorder) -> None:
@@ -38,6 +41,47 @@ def test_recwarn_captures_deprecation_warning(recwarn: WarningsRecorder) -> None
     assert recwarn.pop(DeprecationWarning)
 
 
+class TestSubclassWarningPop:
+    class ParentWarning(Warning):
+        pass
+
+    class ChildWarning(ParentWarning):
+        pass
+
+    class ChildOfChildWarning(ChildWarning):
+        pass
+
+    @staticmethod
+    def raise_warnings_from_list(_warnings: list[type[Warning]]):
+        for warn in _warnings:
+            warnings.warn(f"Warning {warn().__repr__()}", warn)
+
+    def test_pop_finds_exact_match(self):
+        with pytest.warns((self.ParentWarning, self.ChildWarning)) as record:
+            self.raise_warnings_from_list(
+                [self.ChildWarning, self.ParentWarning, self.ChildOfChildWarning]
+            )
+
+        assert len(record) == 3
+        _warn = record.pop(self.ParentWarning)
+        assert _warn.category is self.ParentWarning
+
+    def test_pop_raises_if_no_match(self):
+        with pytest.raises(AssertionError):
+            with pytest.warns(self.ParentWarning) as record:
+                self.raise_warnings_from_list([self.ParentWarning])
+            record.pop(self.ChildOfChildWarning)
+
+    def test_pop_finds_best_inexact_match(self):
+        with pytest.warns(self.ParentWarning) as record:
+            self.raise_warnings_from_list(
+                [self.ChildOfChildWarning, self.ChildWarning, self.ChildOfChildWarning]
+            )
+
+        _warn = record.pop(self.ParentWarning)
+        assert _warn.category is self.ChildWarning
+
+
 class TestWarningsRecorderChecker:
     def test_recording(self) -> None:
         rec = WarningsRecorder(_ispytest=True)
@@ -88,7 +132,7 @@ def test_invalid_enter_exit(self) -> None:
 class TestDeprecatedCall:
     """test pytest.deprecated_call()"""
 
-    def dep(self, i: int, j: Optional[int] = None) -> int:
+    def dep(self, i: int, j: int | None = None) -> int:
         if i == 0:
             warnings.warn("is deprecated", DeprecationWarning, stacklevel=1)
         return 42
@@ -114,13 +158,13 @@ def test_deprecated_call_preserves(self) -> None:
         # Type ignored because `onceregistry` and `filters` are not
         # documented API.
         onceregistry = warnings.onceregistry.copy()  # type: ignore
-        filters = warnings.filters[:]  # type: ignore
+        filters = warnings.filters[:]
         warn = warnings.warn
         warn_explicit = warnings.warn_explicit
         self.test_deprecated_call_raises()
         self.test_deprecated_call()
         assert onceregistry == warnings.onceregistry  # type: ignore
-        assert filters == warnings.filters  # type: ignore
+        assert filters == warnings.filters
         assert warn is warnings.warn
         assert warn_explicit is warnings.warn_explicit
 
@@ -150,7 +194,7 @@ def f():
                     f()
 
     @pytest.mark.parametrize(
-        "warning_type", [PendingDeprecationWarning, DeprecationWarning]
+        "warning_type", [PendingDeprecationWarning, DeprecationWarning, FutureWarning]
     )
     @pytest.mark.parametrize("mode", ["context_manager", "call"])
     @pytest.mark.parametrize("call_f_first", [True, False])
@@ -173,50 +217,35 @@ def f():
             with pytest.deprecated_call():
                 assert f() == 10
 
-    @pytest.mark.parametrize("mode", ["context_manager", "call"])
-    def test_deprecated_call_exception_is_raised(self, mode) -> None:
-        """If the block of the code being tested by deprecated_call() raises an exception,
-        it must raise the exception undisturbed.
-        """
-
-        def f():
-            raise ValueError("some exception")
-
-        with pytest.raises(ValueError, match="some exception"):
-            if mode == "call":
-                pytest.deprecated_call(f)
-            else:
-                with pytest.deprecated_call():
-                    f()
-
     def test_deprecated_call_specificity(self) -> None:
         other_warnings = [
             Warning,
             UserWarning,
             SyntaxWarning,
             RuntimeWarning,
-            FutureWarning,
             ImportWarning,
             UnicodeWarning,
         ]
         for warning in other_warnings:
 
             def f():
-                warnings.warn(warning("hi"))
+                warnings.warn(warning("hi"))  # noqa: B023
 
-            with pytest.raises(pytest.fail.Exception):
-                pytest.deprecated_call(f)
-            with pytest.raises(pytest.fail.Exception):
-                with pytest.deprecated_call():
-                    f()
+            with pytest.warns(warning):
+                with pytest.raises(pytest.fail.Exception):
+                    pytest.deprecated_call(f)
+                with pytest.raises(pytest.fail.Exception):
+                    with pytest.deprecated_call():
+                        f()
 
     def test_deprecated_call_supports_match(self) -> None:
         with pytest.deprecated_call(match=r"must be \d+$"):
             warnings.warn("value must be 42", DeprecationWarning)
 
-        with pytest.raises(pytest.fail.Exception):
-            with pytest.deprecated_call(match=r"must be \d+$"):
-                warnings.warn("this is not here", DeprecationWarning)
+        with pytest.deprecated_call():
+            with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
+                with pytest.deprecated_call(match=r"must be \d+$"):
+                    warnings.warn("this is not here", DeprecationWarning)
 
 
 class TestWarns:
@@ -228,8 +257,9 @@ def test_check_callable(self) -> None:
     def test_several_messages(self) -> None:
         # different messages, b/c Python suppresses multiple identical warnings
         pytest.warns(RuntimeWarning, lambda: warnings.warn("w1", RuntimeWarning))
-        with pytest.raises(pytest.fail.Exception):
-            pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning))
+        with pytest.warns(RuntimeWarning):
+            with pytest.raises(pytest.fail.Exception):
+                pytest.warns(UserWarning, lambda: warnings.warn("w2", RuntimeWarning))
         pytest.warns(RuntimeWarning, lambda: warnings.warn("w3", RuntimeWarning))
 
     def test_function(self) -> None:
@@ -244,13 +274,14 @@ def test_warning_tuple(self) -> None:
         pytest.warns(
             (RuntimeWarning, SyntaxWarning), lambda: warnings.warn("w2", SyntaxWarning)
         )
-        pytest.raises(
-            pytest.fail.Exception,
-            lambda: pytest.warns(
-                (RuntimeWarning, SyntaxWarning),
-                lambda: warnings.warn("w3", UserWarning),
-            ),
-        )
+        with pytest.warns():
+            pytest.raises(
+                pytest.fail.Exception,
+                lambda: pytest.warns(
+                    (RuntimeWarning, SyntaxWarning),
+                    lambda: warnings.warn("w3", UserWarning),
+                ),
+            )
 
     def test_as_contextmanager(self) -> None:
         with pytest.warns(RuntimeWarning):
@@ -259,48 +290,47 @@ def test_as_contextmanager(self) -> None:
         with pytest.warns(UserWarning):
             warnings.warn("user", UserWarning)
 
-        with pytest.raises(pytest.fail.Exception) as excinfo:
-            with pytest.warns(RuntimeWarning):
-                warnings.warn("user", UserWarning)
+        with pytest.warns():
+            with pytest.raises(pytest.fail.Exception) as excinfo:
+                with pytest.warns(RuntimeWarning):
+                    warnings.warn("user", UserWarning)
         excinfo.match(
-            r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted. "
-            r"The list of emitted warnings is: \[UserWarning\('user',?\)\]."
+            r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted.\n"
+            r" Emitted warnings: \[UserWarning\('user',?\)\]."
         )
 
-        with pytest.raises(pytest.fail.Exception) as excinfo:
-            with pytest.warns(UserWarning):
-                warnings.warn("runtime", RuntimeWarning)
+        with pytest.warns():
+            with pytest.raises(pytest.fail.Exception) as excinfo:
+                with pytest.warns(UserWarning):
+                    warnings.warn("runtime", RuntimeWarning)
         excinfo.match(
-            r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. "
-            r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\]."
+            r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n"
+            r" Emitted warnings: \[RuntimeWarning\('runtime',?\)]."
         )
 
         with pytest.raises(pytest.fail.Exception) as excinfo:
             with pytest.warns(UserWarning):
                 pass
         excinfo.match(
-            r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. "
-            r"The list of emitted warnings is: \[\]."
+            r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n"
+            r" Emitted warnings: \[\]."
         )
 
         warning_classes = (UserWarning, FutureWarning)
-        with pytest.raises(pytest.fail.Exception) as excinfo:
-            with pytest.warns(warning_classes) as warninfo:
-                warnings.warn("runtime", RuntimeWarning)
-                warnings.warn("import", ImportWarning)
+        with pytest.warns():
+            with pytest.raises(pytest.fail.Exception) as excinfo:
+                with pytest.warns(warning_classes) as warninfo:
+                    warnings.warn("runtime", RuntimeWarning)
+                    warnings.warn("import", ImportWarning)
 
-        message_template = (
-            "DID NOT WARN. No warnings of type {0} were emitted. "
-            "The list of emitted warnings is: {1}."
-        )
-        excinfo.match(
-            re.escape(
-                message_template.format(
-                    warning_classes, [each.message for each in warninfo]
-                )
-            )
+        messages = [each.message for each in warninfo]
+        expected_str = (
+            f"DID NOT WARN. No warnings of type {warning_classes} were emitted.\n"
+            f" Emitted warnings: {messages}."
         )
 
+        assert str(excinfo.value) == expected_str
+
     def test_record(self) -> None:
         with pytest.warns(UserWarning) as record:
             warnings.warn("user", UserWarning)
@@ -317,17 +347,9 @@ def test_record_only(self) -> None:
         assert str(record[0].message) == "user"
         assert str(record[1].message) == "runtime"
 
-    def test_record_only_none_deprecated_warn(self) -> None:
-        # This should become an error when WARNS_NONE_ARG is removed in Pytest 8.0
-        with warnings.catch_warnings():
-            warnings.simplefilter("ignore")
-            with pytest.warns(None) as record:  # type: ignore[call-overload]
-                warnings.warn("user", UserWarning)
-                warnings.warn("runtime", RuntimeWarning)
-
-            assert len(record) == 2
-            assert str(record[0].message) == "user"
-            assert str(record[1].message) == "runtime"
+    def test_record_only_none_type_error(self) -> None:
+        with pytest.raises(TypeError):
+            pytest.warns(None)  # type: ignore[call-overload]
 
     def test_record_by_subclass(self) -> None:
         with pytest.warns(Warning) as record:
@@ -372,25 +394,31 @@ def test_match_regex(self) -> None:
         with pytest.warns(UserWarning, match=r"must be \d+$"):
             warnings.warn("value must be 42", UserWarning)
 
-        with pytest.raises(pytest.fail.Exception):
-            with pytest.warns(UserWarning, match=r"must be \d+$"):
-                warnings.warn("this is not here", UserWarning)
+        with pytest.warns():
+            with pytest.raises(pytest.fail.Exception):
+                with pytest.warns(UserWarning, match=r"must be \d+$"):
+                    warnings.warn("this is not here", UserWarning)
 
-        with pytest.raises(pytest.fail.Exception):
-            with pytest.warns(FutureWarning, match=r"must be \d+$"):
-                warnings.warn("value must be 42", UserWarning)
+        with pytest.warns():
+            with pytest.raises(pytest.fail.Exception):
+                with pytest.warns(FutureWarning, match=r"must be \d+$"):
+                    warnings.warn("value must be 42", UserWarning)
 
     def test_one_from_multiple_warns(self) -> None:
-        with pytest.warns(UserWarning, match=r"aaa"):
-            warnings.warn("cccccccccc", UserWarning)
-            warnings.warn("bbbbbbbbbb", UserWarning)
-            warnings.warn("aaaaaaaaaa", UserWarning)
+        with pytest.warns():
+            with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
+                with pytest.warns(UserWarning, match=r"aaa"):
+                    with pytest.warns(UserWarning, match=r"aaa"):
+                        warnings.warn("cccccccccc", UserWarning)
+                        warnings.warn("bbbbbbbbbb", UserWarning)
+                        warnings.warn("aaaaaaaaaa", UserWarning)
 
     def test_none_of_multiple_warns(self) -> None:
-        with pytest.raises(pytest.fail.Exception):
-            with pytest.warns(UserWarning, match=r"aaa"):
-                warnings.warn("bbbbbbbbbb", UserWarning)
-                warnings.warn("cccccccccc", UserWarning)
+        with pytest.warns():
+            with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
+                with pytest.warns(UserWarning, match=r"aaa"):
+                    warnings.warn("bbbbbbbbbb", UserWarning)
+                    warnings.warn("cccccccccc", UserWarning)
 
     @pytest.mark.filterwarnings("ignore")
     def test_can_capture_previously_warned(self) -> None:
@@ -408,3 +436,160 @@ def test_warns_context_manager_with_kwargs(self) -> None:
             with pytest.warns(UserWarning, foo="bar"):  # type: ignore
                 pass
         assert "Unexpected keyword arguments" in str(excinfo.value)
+
+    def test_re_emit_single(self) -> None:
+        with pytest.warns(DeprecationWarning):
+            with pytest.warns(UserWarning):
+                warnings.warn("user warning", UserWarning)
+                warnings.warn("some deprecation warning", DeprecationWarning)
+
+    def test_re_emit_multiple(self) -> None:
+        with pytest.warns(UserWarning):
+            warnings.warn("first warning", UserWarning)
+            warnings.warn("second warning", UserWarning)
+
+    def test_re_emit_match_single(self) -> None:
+        with pytest.warns(DeprecationWarning):
+            with pytest.warns(UserWarning, match="user warning"):
+                warnings.warn("user warning", UserWarning)
+                warnings.warn("some deprecation warning", DeprecationWarning)
+
+    def test_re_emit_match_multiple(self) -> None:
+        with warnings.catch_warnings():
+            warnings.simplefilter("error")  # if anything is re-emitted
+            with pytest.warns(UserWarning, match="user warning"):
+                warnings.warn("first user warning", UserWarning)
+                warnings.warn("second user warning", UserWarning)
+
+    def test_re_emit_non_match_single(self) -> None:
+        with pytest.warns(UserWarning, match="v2 warning"):
+            with pytest.warns(UserWarning, match="v1 warning"):
+                warnings.warn("v1 warning", UserWarning)
+                warnings.warn("non-matching v2 warning", UserWarning)
+
+    def test_catch_warning_within_raise(self) -> None:
+        # warns-in-raises works since https://github.com/pytest-dev/pytest/pull/11129
+        with pytest.raises(ValueError, match="some exception"):
+            with pytest.warns(FutureWarning, match="some warning"):
+                warnings.warn("some warning", category=FutureWarning)
+                raise ValueError("some exception")
+        # and raises-in-warns has always worked but we'll check for symmetry.
+        with pytest.warns(FutureWarning, match="some warning"):
+            with pytest.raises(ValueError, match="some exception"):
+                warnings.warn("some warning", category=FutureWarning)
+                raise ValueError("some exception")
+
+    def test_skip_within_warns(self, pytester: Pytester) -> None:
+        """Regression test for #11907."""
+        pytester.makepyfile(
+            """
+            import pytest
+
+            def test_it():
+                with pytest.warns(Warning):
+                    pytest.skip("this is OK")
+            """,
+        )
+
+        result = pytester.runpytest()
+        assert result.ret == ExitCode.OK
+        result.assert_outcomes(skipped=1)
+
+    def test_fail_within_warns(self, pytester: Pytester) -> None:
+        """Regression test for #11907."""
+        pytester.makepyfile(
+            """
+            import pytest
+
+            def test_it():
+                with pytest.warns(Warning):
+                    pytest.fail("BOOM")
+            """,
+        )
+
+        result = pytester.runpytest()
+        assert result.ret == ExitCode.TESTS_FAILED
+        result.assert_outcomes(failed=1)
+        assert "DID NOT WARN" not in str(result.stdout)
+
+    def test_exit_within_warns(self, pytester: Pytester) -> None:
+        """Regression test for #11907."""
+        pytester.makepyfile(
+            """
+            import pytest
+
+            def test_it():
+                with pytest.warns(Warning):
+                    pytest.exit()
+            """,
+        )
+
+        result = pytester.runpytest()
+        assert result.ret == ExitCode.INTERRUPTED
+        result.assert_outcomes()
+
+    def test_keyboard_interrupt_within_warns(self, pytester: Pytester) -> None:
+        """Regression test for #11907."""
+        pytester.makepyfile(
+            """
+            import pytest
+
+            def test_it():
+                with pytest.warns(Warning):
+                    raise KeyboardInterrupt()
+            """,
+        )
+
+        result = pytester.runpytest_subprocess()
+        assert result.ret == ExitCode.INTERRUPTED
+        result.assert_outcomes()
+
+
+def test_raise_type_error_on_invalid_warning() -> None:
+    """Check pytest.warns validates warning messages are strings (#10865) or
+    Warning instances (#11959)."""
+    with pytest.raises(TypeError, match="Warning must be str or Warning"):
+        with pytest.warns(UserWarning):
+            warnings.warn(1)  # type: ignore
+
+
+@pytest.mark.parametrize(
+    "message",
+    [
+        pytest.param("Warning", id="str"),
+        pytest.param(UserWarning(), id="UserWarning"),
+        pytest.param(Warning(), id="Warning"),
+    ],
+)
+def test_no_raise_type_error_on_valid_warning(message: str | Warning) -> None:
+    """Check pytest.warns validates warning messages are strings (#10865) or
+    Warning instances (#11959)."""
+    with pytest.warns(Warning):
+        warnings.warn(message)
+
+
+@pytest.mark.skipif(
+    hasattr(sys, "pypy_version_info"),
+    reason="Not for pypy",
+)
+def test_raise_type_error_on_invalid_warning_message_cpython() -> None:
+    # Check that we get the same behavior with the stdlib, at least if filtering
+    # (see https://github.com/python/cpython/issues/103577 for details)
+    with pytest.raises(TypeError):
+        with warnings.catch_warnings():
+            warnings.filterwarnings("ignore", "test")
+            warnings.warn(1)  # type: ignore
+
+
+def test_multiple_arg_custom_warning() -> None:
+    """Test for issue #11906."""
+
+    class CustomWarning(UserWarning):
+        def __init__(self, a, b):
+            pass
+
+    with pytest.warns(CustomWarning):
+        with pytest.raises(pytest.fail.Exception, match="DID NOT WARN"):
+            with pytest.warns(CustomWarning, match="not gonna match"):
+                a, b = 1, 2
+                warnings.warn(CustomWarning(a, b))
diff --git a/testing/test_reports.py b/testing/test_reports.py
index 31b6cf1afc6..7a893981838 100644
--- a/testing/test_reports.py
+++ b/testing/test_reports.py
@@ -1,13 +1,16 @@
-from typing import Sequence
-from typing import Union
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Sequence
 
-import pytest
 from _pytest._code.code import ExceptionChainRepr
 from _pytest._code.code import ExceptionRepr
 from _pytest.config import Config
 from _pytest.pytester import Pytester
+from _pytest.python_api import approx
 from _pytest.reports import CollectReport
 from _pytest.reports import TestReport
+import pytest
 
 
 class TestReportSerialization:
@@ -98,14 +101,13 @@ def test_repr_entry():
 
         rep_entries = rep.longrepr.reprtraceback.reprentries
         a_entries = a.longrepr.reprtraceback.reprentries
-        for i in range(len(a_entries)):
-            rep_entry = rep_entries[i]
+        assert len(rep_entries) == len(a_entries)  # python < 3.10 zip(strict=True)
+        for a_entry, rep_entry in zip(a_entries, rep_entries):
             assert isinstance(rep_entry, ReprEntry)
             assert rep_entry.reprfileloc is not None
             assert rep_entry.reprfuncargs is not None
             assert rep_entry.reprlocals is not None
 
-            a_entry = a_entries[i]
             assert isinstance(a_entry, ReprEntry)
             assert a_entry.reprfileloc is not None
             assert a_entry.reprfuncargs is not None
@@ -144,9 +146,10 @@ def test_repr_entry_native():
 
         rep_entries = rep.longrepr.reprtraceback.reprentries
         a_entries = a.longrepr.reprtraceback.reprentries
-        for i in range(len(a_entries)):
-            assert isinstance(rep_entries[i], ReprEntryNative)
-            assert rep_entries[i].lines == a_entries[i].lines
+        assert len(rep_entries) == len(a_entries)  # python < 3.10 zip(strict=True)
+        for rep_entry, a_entry in zip(rep_entries, a_entries):
+            assert isinstance(rep_entry, ReprEntryNative)
+            assert rep_entry.lines == a_entry.lines
 
     def test_itemreport_outcomes(self, pytester: Pytester) -> None:
         # This test came originally from test_remote.py in xdist (ca03269).
@@ -277,7 +280,7 @@ def test_chained_exceptions(
     ) -> None:
         """Check serialization/deserialization of report objects containing chained exceptions (#5786)"""
         pytester.makepyfile(
-            """
+            f"""
             def foo():
                 raise ValueError('value error')
             def test_a():
@@ -285,27 +288,25 @@ def test_a():
                     foo()
                 except ValueError as e:
                     raise RuntimeError('runtime error') from e
-            if {error_during_import}:
+            if {report_class is CollectReport}:
                 test_a()
-        """.format(
-                error_during_import=report_class is CollectReport
-            )
+        """
         )
 
         reprec = pytester.inline_run()
         if report_class is TestReport:
-            reports: Union[
-                Sequence[TestReport], Sequence[CollectReport]
-            ] = reprec.getreports("pytest_runtest_logreport")
+            reports: Sequence[TestReport] | Sequence[CollectReport] = reprec.getreports(
+                "pytest_runtest_logreport"
+            )
             # we have 3 reports: setup/call/teardown
             assert len(reports) == 3
             # get the call report
             report = reports[1]
         else:
             assert report_class is CollectReport
-            # two collection reports: session and test file
+            # three collection reports: session, test file, directory
             reports = reprec.getreports("pytest_collectreport")
-            assert len(reports) == 2
+            assert len(reports) == 3
             report = reports[1]
 
         def check_longrepr(longrepr: ExceptionChainRepr) -> None:
@@ -409,12 +410,32 @@ def test_report_prevent_ConftestImportFailure_hiding_exception(
     ) -> None:
         sub_dir = pytester.path.joinpath("ns")
         sub_dir.mkdir()
-        sub_dir.joinpath("conftest.py").write_text("import unknown")
+        sub_dir.joinpath("conftest.py").write_text("import unknown", encoding="utf-8")
 
         result = pytester.runpytest_subprocess(".")
         result.stdout.fnmatch_lines(["E   *Error: No module named 'unknown'"])
         result.stdout.no_fnmatch_line("ERROR  - *ConftestImportFailure*")
 
+    def test_report_timestamps_match_duration(self, pytester: Pytester, mock_timing):
+        reprec = pytester.inline_runsource(
+            """
+            import pytest
+            from _pytest import timing
+            @pytest.fixture
+            def fixture_():
+                timing.sleep(5)
+                yield
+                timing.sleep(5)
+            def test_1(fixture_): timing.sleep(10)
+        """
+        )
+        reports = reprec.getreports("pytest_runtest_logreport")
+        assert len(reports) == 3
+        for report in reports:
+            data = report._to_json()
+            loaded_report = TestReport._from_json(data)
+            assert loaded_report.stop - loaded_report.start == approx(report.duration)
+
 
 class TestHooks:
     """Test that the hooks are working correctly for plugins"""
@@ -450,7 +471,7 @@ def test_b(): pass
         )
         reprec = pytester.inline_run()
         reports = reprec.getreports("pytest_collectreport")
-        assert len(reports) == 2
+        assert len(reports) == 3
         for rep in reports:
             data = pytestconfig.hook.pytest_report_to_serializable(
                 config=pytestconfig, report=rep
diff --git a/testing/test_runner.py b/testing/test_runner.py
index a34cd98f964..0245438a47d 100644
--- a/testing/test_runner.py
+++ b/testing/test_runner.py
@@ -1,14 +1,14 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from functools import partial
 import inspect
 import os
+from pathlib import Path
 import sys
 import types
-from pathlib import Path
-from typing import Dict
-from typing import List
-from typing import Tuple
-from typing import Type
+import warnings
 
-import pytest
 from _pytest import outcomes
 from _pytest import reports
 from _pytest import runner
@@ -18,6 +18,11 @@
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.outcomes import OutcomeException
 from _pytest.pytester import Pytester
+import pytest
+
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import ExceptionGroup
 
 
 class TestSetupState:
@@ -77,8 +82,6 @@ def fin3():
         assert r == ["fin3", "fin1"]
 
     def test_teardown_multiple_fail(self, pytester: Pytester) -> None:
-        # Ensure the first exception is the one which is re-raised.
-        # Ideally both would be reported however.
         def fin1():
             raise Exception("oops1")
 
@@ -90,9 +93,14 @@ def fin2():
         ss.setup(item)
         ss.addfinalizer(fin1, item)
         ss.addfinalizer(fin2, item)
-        with pytest.raises(Exception) as err:
+        with pytest.raises(ExceptionGroup) as err:
             ss.teardown_exact(None)
-        assert err.value.args == ("oops2",)
+
+        # Note that finalizers are run LIFO, but because FIFO is more intuitive for
+        # users we reverse the order of messages, and see the error from fin1 first.
+        err1, err2 = err.value.exceptions
+        assert err1.args == ("oops1",)
+        assert err2.args == ("oops2",)
 
     def test_teardown_multiple_scopes_one_fails(self, pytester: Pytester) -> None:
         module_teardown = []
@@ -113,6 +121,62 @@ def fin_module():
             ss.teardown_exact(None)
         assert module_teardown == ["fin_module"]
 
+    def test_teardown_multiple_scopes_several_fail(self, pytester) -> None:
+        def raiser(exc):
+            raise exc
+
+        item = pytester.getitem("def test_func(): pass")
+        mod = item.listchain()[-2]
+        ss = item.session._setupstate
+        ss.setup(item)
+        ss.addfinalizer(partial(raiser, KeyError("from module scope")), mod)
+        ss.addfinalizer(partial(raiser, TypeError("from function scope 1")), item)
+        ss.addfinalizer(partial(raiser, ValueError("from function scope 2")), item)
+
+        with pytest.raises(ExceptionGroup, match="errors during test teardown") as e:
+            ss.teardown_exact(None)
+        mod, func = e.value.exceptions
+        assert isinstance(mod, KeyError)
+        assert isinstance(func.exceptions[0], TypeError)
+        assert isinstance(func.exceptions[1], ValueError)
+
+    def test_cached_exception_doesnt_get_longer(self, pytester: Pytester) -> None:
+        """Regression test for #12204 (the "BTW" case)."""
+        pytester.makepyfile(test="")
+        # If the collector.setup() raises, all collected items error with this
+        # exception.
+        pytester.makeconftest(
+            """
+            import pytest
+
+            class MyItem(pytest.Item):
+                def runtest(self) -> None: pass
+
+            class MyBadCollector(pytest.Collector):
+                def collect(self):
+                    return [
+                        MyItem.from_parent(self, name="one"),
+                        MyItem.from_parent(self, name="two"),
+                        MyItem.from_parent(self, name="three"),
+                    ]
+
+                def setup(self):
+                    1 / 0
+
+            def pytest_collect_file(file_path, parent):
+                if file_path.name == "test.py":
+                    return MyBadCollector.from_parent(parent, name='bad')
+            """
+        )
+
+        result = pytester.runpytest_inprocess("--tb=native")
+        assert result.ret == ExitCode.TESTS_FAILED
+        failures = result.reprec.getfailures()  # type: ignore[attr-defined]
+        assert len(failures) == 3
+        lines1 = failures[1].longrepr.reprtraceback.reprentries[0].lines
+        lines2 = failures[2].longrepr.reprtraceback.reprentries[0].lines
+        assert len(lines1) == len(lines2)
+
 
 class BaseFunctionalTests:
     def test_passfunction(self, pytester: Pytester) -> None:
@@ -287,7 +351,7 @@ def teardown_class(cls):
 
             def test_func():
                 import sys
-                # on python2 exc_info is keept till a function exits
+                # on python2 exc_info is kept till a function exits
                 # so we would end up calling test functions while
                 # sys.exc_info would return the indexerror
                 # from guessing the lastitem
@@ -380,7 +444,7 @@ def test_func():
         # assert rep.outcome.when == "setup"
         # assert rep.outcome.where.lineno == 3
         # assert rep.outcome.where.path.basename == "test_func.py"
-        # assert instanace(rep.failed.failurerepr, PythonFailureRepr)
+        # assert isinstance(rep.failed.failurerepr, PythonFailureRepr)
 
     def test_systemexit_does_not_bail_out(self, pytester: Pytester) -> None:
         try:
@@ -447,6 +511,7 @@ class TestClass(object):
         assert not rep.skipped
         assert rep.passed
         locinfo = rep.location
+        assert locinfo is not None
         assert locinfo[0] == col.path.name
         assert not locinfo[1]
         assert locinfo[2] == col.path.name
@@ -456,7 +521,7 @@ class TestClass(object):
         assert res[1].name == "TestClass"
 
 
-reporttypes: List[Type[reports.BaseReport]] = [
+reporttypes: list[type[reports.BaseReport]] = [
     reports.BaseReport,
     reports.TestReport,
     reports.CollectReport,
@@ -466,9 +531,9 @@ class TestClass(object):
 @pytest.mark.parametrize(
     "reporttype", reporttypes, ids=[x.__name__ for x in reporttypes]
 )
-def test_report_extra_parameters(reporttype: Type[reports.BaseReport]) -> None:
+def test_report_extra_parameters(reporttype: type[reports.BaseReport]) -> None:
     args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:]
-    basekw: Dict[str, List[object]] = dict.fromkeys(args, [])
+    basekw: dict[str, list[object]] = {arg: [] for arg in args}
     report = reporttype(newthing=1, **basekw)
     assert report.newthing == 1
 
@@ -515,10 +580,10 @@ def pytest_runtest_setup(self, item):
             @pytest.fixture
             def mylist(self, request):
                 return request.function.mylist
-            @pytest.hookimpl(hookwrapper=True)
+            @pytest.hookimpl(wrapper=True)
             def pytest_runtest_call(self, item):
                 try:
-                    (yield).get_result()
+                    yield
                 except ValueError:
                     pass
             def test_hello1(self, mylist):
@@ -733,6 +798,73 @@ def test_importorskip_imports_last_module_part() -> None:
     assert os.path == ospath
 
 
+class TestImportOrSkipExcType:
+    """Tests for #11523."""
+
+    def test_no_warning(self) -> None:
+        # An attempt on a module which does not exist will raise ModuleNotFoundError, so it will
+        # be skipped normally and no warning will be issued.
+        with warnings.catch_warnings(record=True) as captured:
+            warnings.simplefilter("always")
+
+            with pytest.raises(pytest.skip.Exception):
+                pytest.importorskip("TestImportOrSkipExcType_test_no_warning")
+
+        assert captured == []
+
+    def test_import_error_with_warning(self, pytester: Pytester) -> None:
+        # Create a module which exists and can be imported, however it raises
+        # ImportError due to some other problem. In this case we will issue a warning
+        # about the future behavior change.
+        fn = pytester.makepyfile("raise ImportError('some specific problem')")
+        pytester.syspathinsert()
+
+        with warnings.catch_warnings(record=True) as captured:
+            warnings.simplefilter("always")
+
+            with pytest.raises(pytest.skip.Exception):
+                pytest.importorskip(fn.stem)
+
+        [warning] = captured
+        assert warning.category is pytest.PytestDeprecationWarning
+
+    def test_import_error_suppress_warning(self, pytester: Pytester) -> None:
+        # Same as test_import_error_with_warning, but we can suppress the warning
+        # by passing ImportError as exc_type.
+        fn = pytester.makepyfile("raise ImportError('some specific problem')")
+        pytester.syspathinsert()
+
+        with warnings.catch_warnings(record=True) as captured:
+            warnings.simplefilter("always")
+
+            with pytest.raises(pytest.skip.Exception):
+                pytest.importorskip(fn.stem, exc_type=ImportError)
+
+        assert captured == []
+
+    def test_warning_integration(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """
+            import pytest
+            def test_foo():
+                pytest.importorskip("warning_integration_module")
+            """
+        )
+        pytester.makepyfile(
+            warning_integration_module="""
+                raise ImportError("required library foobar not compiled properly")
+            """
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(
+            [
+                "*Module 'warning_integration_module' was found, but when imported by pytest it raised:",
+                "*      ImportError('required library foobar not compiled properly')",
+                "*1 skipped, 1 warning*",
+            ]
+        )
+
+
 def test_importorskip_dev_module(monkeypatch) -> None:
     try:
         mod = types.ModuleType("mockmodule")
@@ -799,12 +931,12 @@ def test_unicode_in_longrepr(pytester: Pytester) -> None:
     pytester.makeconftest(
         """\
         import pytest
-        @pytest.hookimpl(hookwrapper=True)
+        @pytest.hookimpl(wrapper=True)
         def pytest_runtest_makereport():
-            outcome = yield
-            rep = outcome.get_result()
+            rep = yield
             if rep.when == "call":
                 rep.longrepr = 'ä'
+            return rep
         """
     )
     pytester.makepyfile(
@@ -880,6 +1012,7 @@ def test_fix(foo):
 def test_store_except_info_on_error() -> None:
     """Test that upon test failure, the exception info is stored on
     sys.last_traceback and friends."""
+
     # Simulate item that might raise a specific exception, depending on `raise_error` class var
     class ItemMightRaise:
         nodeid = "item_that_raises"
@@ -896,6 +1029,9 @@ def runtest(self):
     # Check that exception info is stored on sys
     assert sys.last_type is IndexError
     assert isinstance(sys.last_value, IndexError)
+    if sys.version_info >= (3, 12, 0):
+        assert isinstance(sys.last_exc, IndexError)  # type:ignore[attr-defined]
+
     assert sys.last_value.args[0] == "TEST"
     assert sys.last_traceback
 
@@ -904,11 +1040,13 @@ def runtest(self):
     runner.pytest_runtest_call(ItemMightRaise())  # type: ignore[arg-type]
     assert not hasattr(sys, "last_type")
     assert not hasattr(sys, "last_value")
+    if sys.version_info >= (3, 12, 0):
+        assert not hasattr(sys, "last_exc")
     assert not hasattr(sys, "last_traceback")
 
 
 def test_current_test_env_var(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
-    pytest_current_test_vars: List[Tuple[str, str]] = []
+    pytest_current_test_vars: list[tuple[str, str]] = []
     monkeypatch.setattr(
         sys, "pytest_current_test_vars", pytest_current_test_vars, raising=False
     )
@@ -978,7 +1116,7 @@ def test_longreprtext_collect_skip(self, pytester: Pytester) -> None:
         )
         rec = pytester.inline_run()
         calls = rec.getcalls("pytest_collectreport")
-        _, call = calls
+        _, call, _ = calls
         assert isinstance(call.report.longrepr, tuple)
         assert "Skipped" in call.report.longreprtext
 
@@ -1059,3 +1197,70 @@ def func() -> None:
     with pytest.raises(TypeError) as excinfo:
         OutcomeException(func)  # type: ignore
     assert str(excinfo.value) == expected
+
+
+def test_pytest_version_env_var(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
+    monkeypatch.setenv("PYTEST_VERSION", "old version")
+    pytester.makepyfile(
+        """
+        import pytest
+        import os
+
+
+        def test():
+            assert os.environ.get("PYTEST_VERSION") == pytest.__version__
+    """
+    )
+    result = pytester.runpytest_inprocess()
+    assert result.ret == ExitCode.OK
+    assert os.environ["PYTEST_VERSION"] == "old version"
+
+
+def test_teardown_session_failed(pytester: Pytester) -> None:
+    """Test that higher-scoped fixture teardowns run in the context of the last
+    item after the test session bails early due to --maxfail.
+
+    Regression test for #11706.
+    """
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture(scope="module")
+        def baz():
+            yield
+            pytest.fail("This is a failing teardown")
+
+        def test_foo(baz):
+            pytest.fail("This is a failing test")
+
+        def test_bar(): pass
+        """
+    )
+    result = pytester.runpytest("--maxfail=1")
+    result.assert_outcomes(failed=1, errors=1)
+
+
+def test_teardown_session_stopped(pytester: Pytester) -> None:
+    """Test that higher-scoped fixture teardowns run in the context of the last
+    item after the test session bails early due to --stepwise.
+
+    Regression test for #11706.
+    """
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture(scope="module")
+        def baz():
+            yield
+            pytest.fail("This is a failing teardown")
+
+        def test_foo(baz):
+            pytest.fail("This is a failing test")
+
+        def test_bar(): pass
+        """
+    )
+    result = pytester.runpytest("--stepwise")
+    result.assert_outcomes(failed=1, errors=1)
diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py
index e077ac41e2c..75e838a49e8 100644
--- a/testing/test_runner_xunit.py
+++ b/testing/test_runner_xunit.py
@@ -1,8 +1,10 @@
+# mypy: allow-untyped-defs
 """Test correct setup/teardowns at module, class, and instance level."""
-from typing import List
 
-import pytest
+from __future__ import annotations
+
 from _pytest.pytester import Pytester
+import pytest
 
 
 def test_module_and_function_setup(pytester: Pytester) -> None:
@@ -249,12 +251,12 @@ def test_setup_teardown_function_level_with_optional_argument(
     """Parameter to setup/teardown xunit-style functions parameter is now optional (#1728)."""
     import sys
 
-    trace_setups_teardowns: List[str] = []
+    trace_setups_teardowns: list[str] = []
     monkeypatch.setattr(
         sys, "trace_setups_teardowns", trace_setups_teardowns, raising=False
     )
     p = pytester.makepyfile(
-        """
+        f"""
         import pytest
         import sys
 
@@ -275,9 +277,7 @@ def teardown_method(self, {arg}): trace('teardown_method')
 
             def test_method_1(self): pass
             def test_method_2(self): pass
-    """.format(
-            arg=arg
-        )
+    """
     )
     result = pytester.inline_run(p)
     result.assertoutcome(passed=4)
diff --git a/testing/test_scope.py b/testing/test_scope.py
index 09ee1343a80..3cb811469a9 100644
--- a/testing/test_scope.py
+++ b/testing/test_scope.py
@@ -1,7 +1,9 @@
+from __future__ import annotations
+
 import re
 
-import pytest
 from _pytest.scope import Scope
+import pytest
 
 
 def test_ordering() -> None:
diff --git a/testing/test_session.py b/testing/test_session.py
index 3ca6d390383..ba904916033 100644
--- a/testing/test_session.py
+++ b/testing/test_session.py
@@ -1,7 +1,10 @@
-import pytest
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 from _pytest.config import ExitCode
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
+import pytest
 
 
 class SessionTests:
@@ -172,8 +175,9 @@ def test_one(): pass
         except pytest.skip.Exception:  # pragma: no cover
             pytest.fail("wrong skipped caught")
         reports = reprec.getreports("pytest_collectreport")
-        assert len(reports) == 1
-        assert reports[0].skipped
+        # Session, Dir
+        assert len(reports) == 2
+        assert reports[1].skipped
 
 
 class TestNewSession(SessionTests):
@@ -265,9 +269,9 @@ def test_plugin_already_exists(pytester: Pytester) -> None:
 
 def test_exclude(pytester: Pytester) -> None:
     hellodir = pytester.mkdir("hello")
-    hellodir.joinpath("test_hello.py").write_text("x y syntaxerror")
+    hellodir.joinpath("test_hello.py").write_text("x y syntaxerror", encoding="utf-8")
     hello2dir = pytester.mkdir("hello2")
-    hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror")
+    hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror", encoding="utf-8")
     pytester.makepyfile(test_ok="def test_pass(): pass")
     result = pytester.runpytest("--ignore=hello", "--ignore=hello2")
     assert result.ret == 0
@@ -276,13 +280,13 @@ def test_exclude(pytester: Pytester) -> None:
 
 def test_exclude_glob(pytester: Pytester) -> None:
     hellodir = pytester.mkdir("hello")
-    hellodir.joinpath("test_hello.py").write_text("x y syntaxerror")
+    hellodir.joinpath("test_hello.py").write_text("x y syntaxerror", encoding="utf-8")
     hello2dir = pytester.mkdir("hello2")
-    hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror")
+    hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror", encoding="utf-8")
     hello3dir = pytester.mkdir("hallo3")
-    hello3dir.joinpath("test_hello3.py").write_text("x y syntaxerror")
+    hello3dir.joinpath("test_hello3.py").write_text("x y syntaxerror", encoding="utf-8")
     subdir = pytester.mkdir("sub")
-    subdir.joinpath("test_hello4.py").write_text("x y syntaxerror")
+    subdir.joinpath("test_hello4.py").write_text("x y syntaxerror", encoding="utf-8")
     pytester.makepyfile(test_ok="def test_pass(): pass")
     result = pytester.runpytest("--ignore-glob=*h[ea]llo*")
     assert result.ret == 0
@@ -335,6 +339,56 @@ def pytest_sessionfinish():
     assert res.ret == ExitCode.NO_TESTS_COLLECTED
 
 
+def test_collection_args_do_not_duplicate_modules(pytester: Pytester) -> None:
+    """Test that when multiple collection args are specified on the command line
+    for the same module, only a single Module collector is created.
+
+    Regression test for #723, #3358.
+    """
+    pytester.makepyfile(
+        **{
+            "d/test_it": """
+                def test_1(): pass
+                def test_2(): pass
+                """
+        }
+    )
+
+    result = pytester.runpytest(
+        "--collect-only",
+        "d/test_it.py::test_1",
+        "d/test_it.py::test_2",
+    )
+    result.stdout.fnmatch_lines(
+        [
+            "  <Dir d>",
+            "    <Module test_it.py>",
+            "      <Function test_1>",
+            "      <Function test_2>",
+        ],
+        consecutive=True,
+    )
+
+    # Different, but related case.
+    result = pytester.runpytest(
+        "--collect-only",
+        "--keep-duplicates",
+        "d",
+        "d",
+    )
+    result.stdout.fnmatch_lines(
+        [
+            "  <Dir d>",
+            "    <Module test_it.py>",
+            "      <Function test_1>",
+            "      <Function test_2>",
+            "      <Function test_1>",
+            "      <Function test_2>",
+        ],
+        consecutive=True,
+    )
+
+
 @pytest.mark.parametrize("path", ["root", "{relative}/root", "{environment}/root"])
 def test_rootdir_option_arg(
     pytester: Pytester, monkeypatch: MonkeyPatch, path: str
@@ -367,3 +421,63 @@ def test_rootdir_wrong_option_arg(pytester: Pytester) -> None:
     result.stderr.fnmatch_lines(
         ["*Directory *wrong_dir* not found. Check your '--rootdir' option.*"]
     )
+
+
+def test_shouldfail_is_sticky(pytester: Pytester) -> None:
+    """Test that session.shouldfail cannot be reset to False after being set.
+
+    Issue #11706.
+    """
+    pytester.makeconftest(
+        """
+        def pytest_sessionfinish(session):
+            assert session.shouldfail
+            session.shouldfail = False
+            assert session.shouldfail
+        """
+    )
+    pytester.makepyfile(
+        """
+        import pytest
+
+        def test_foo():
+            pytest.fail("This is a failing test")
+
+        def test_bar(): pass
+        """
+    )
+
+    result = pytester.runpytest("--maxfail=1", "-Wall")
+
+    result.assert_outcomes(failed=1, warnings=1)
+    result.stdout.fnmatch_lines("*session.shouldfail cannot be unset*")
+
+
+def test_shouldstop_is_sticky(pytester: Pytester) -> None:
+    """Test that session.shouldstop cannot be reset to False after being set.
+
+    Issue #11706.
+    """
+    pytester.makeconftest(
+        """
+        def pytest_sessionfinish(session):
+            assert session.shouldstop
+            session.shouldstop = False
+            assert session.shouldstop
+        """
+    )
+    pytester.makepyfile(
+        """
+        import pytest
+
+        def test_foo():
+            pytest.fail("This is a failing test")
+
+        def test_bar(): pass
+        """
+    )
+
+    result = pytester.runpytest("--stepwise", "-Wall")
+
+    result.assert_outcomes(failed=1, warnings=1)
+    result.stdout.fnmatch_lines("*session.shouldstop cannot be unset*")
diff --git a/testing/test_setuponly.py b/testing/test_setuponly.py
index fe4bdc514eb..87123bd9a16 100644
--- a/testing/test_setuponly.py
+++ b/testing/test_setuponly.py
@@ -1,8 +1,11 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import sys
 
-import pytest
 from _pytest.config import ExitCode
 from _pytest.pytester import Pytester
+import pytest
 
 
 @pytest.fixture(params=["--setup-only", "--setup-plan", "--setup-show"], scope="module")
diff --git a/testing/test_setupplan.py b/testing/test_setupplan.py
index d51a1873959..5a9211d7806 100644
--- a/testing/test_setupplan.py
+++ b/testing/test_setupplan.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from _pytest.pytester import Pytester
 
 
diff --git a/testing/test_skipping.py b/testing/test_skipping.py
index a0b5cddabce..9a6c2c4b6aa 100644
--- a/testing/test_skipping.py
+++ b/testing/test_skipping.py
@@ -1,12 +1,15 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import sys
 import textwrap
 
-import pytest
 from _pytest.pytester import Pytester
 from _pytest.runner import runtestprotocol
 from _pytest.skipping import evaluate_skip_marks
 from _pytest.skipping import evaluate_xfail_marks
 from _pytest.skipping import pytest_runtest_setup
+import pytest
 
 
 class TestEvaluation:
@@ -73,16 +76,15 @@ def test_marked_one_arg_twice(self, pytester: Pytester) -> None:
             """@pytest.mark.skipif("not hasattr(os, 'murks')")""",
             """@pytest.mark.skipif(condition="hasattr(os, 'murks')")""",
         ]
-        for i in range(0, 2):
+        for i in range(2):
             item = pytester.getitem(
-                """
+                f"""
                 import pytest
-                %s
-                %s
+                {lines[i]}
+                {lines[(i + 1) % 2]}
                 def test_func():
                     pass
             """
-                % (lines[i], lines[(i + 1) % 2])
             )
             skipped = evaluate_skip_marks(item)
             assert skipped
@@ -195,7 +197,8 @@ def test_skipif_markeval_namespace_multiple(self, pytester: Pytester) -> None:
             def pytest_markeval_namespace():
                 return {"arg": "root"}
             """
-            )
+            ),
+            encoding="utf-8",
         )
         root.joinpath("test_root.py").write_text(
             textwrap.dedent(
@@ -206,7 +209,8 @@ def pytest_markeval_namespace():
             def test_root():
                 assert False
             """
-            )
+            ),
+            encoding="utf-8",
         )
         foo = root.joinpath("foo")
         foo.mkdir()
@@ -219,7 +223,8 @@ def test_root():
             def pytest_markeval_namespace():
                 return {"arg": "foo"}
             """
-            )
+            ),
+            encoding="utf-8",
         )
         foo.joinpath("test_foo.py").write_text(
             textwrap.dedent(
@@ -230,7 +235,8 @@ def pytest_markeval_namespace():
             def test_foo():
                 assert False
             """
-            )
+            ),
+            encoding="utf-8",
         )
         bar = root.joinpath("bar")
         bar.mkdir()
@@ -243,7 +249,8 @@ def test_foo():
             def pytest_markeval_namespace():
                 return {"arg": "bar"}
             """
-            )
+            ),
+            encoding="utf-8",
         )
         bar.joinpath("test_bar.py").write_text(
             textwrap.dedent(
@@ -254,7 +261,8 @@ def pytest_markeval_namespace():
             def test_bar():
                 assert False
             """
-            )
+            ),
+            encoding="utf-8",
         )
 
         reprec = pytester.inline_run("-vs", "--capture=no")
@@ -291,13 +299,12 @@ class TestXFail:
     @pytest.mark.parametrize("strict", [True, False])
     def test_xfail_simple(self, pytester: Pytester, strict: bool) -> None:
         item = pytester.getitem(
-            """
+            f"""
             import pytest
-            @pytest.mark.xfail(strict=%s)
+            @pytest.mark.xfail(strict={strict})
             def test_func():
                 assert 0
         """
-            % strict
         )
         reports = runtestprotocol(item, log=False)
         assert len(reports) == 3
@@ -441,10 +448,8 @@ def test_this_false():
         result = pytester.runpytest(p, "-rx")
         result.stdout.fnmatch_lines(
             [
-                "*test_one*test_this*",
-                "*NOTRUN*noway",
-                "*test_one*test_this_true*",
-                "*NOTRUN*condition:*True*",
+                "*test_one*test_this - *NOTRUN* noway",
+                "*test_one*test_this_true - *NOTRUN* condition: True",
                 "*1 passed*",
             ]
         )
@@ -461,9 +466,7 @@ def setup_module(mod):
         """
         )
         result = pytester.runpytest(p, "-rx")
-        result.stdout.fnmatch_lines(
-            ["*test_one*test_this*", "*NOTRUN*hello", "*1 xfailed*"]
-        )
+        result.stdout.fnmatch_lines(["*test_one*test_this*NOTRUN*hello", "*1 xfailed*"])
 
     def test_xfail_xpass(self, pytester: Pytester) -> None:
         p = pytester.makepyfile(
@@ -489,7 +492,7 @@ def test_this():
         result = pytester.runpytest(p)
         result.stdout.fnmatch_lines(["*1 xfailed*"])
         result = pytester.runpytest(p, "-rx")
-        result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"])
+        result.stdout.fnmatch_lines(["*XFAIL*test_this*hello*"])
         result = pytester.runpytest(p, "--runxfail")
         result.stdout.fnmatch_lines(["*1 pass*"])
 
@@ -507,7 +510,7 @@ def test_this():
         result = pytester.runpytest(p)
         result.stdout.fnmatch_lines(["*1 xfailed*"])
         result = pytester.runpytest(p, "-rx")
-        result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"])
+        result.stdout.fnmatch_lines(["*XFAIL*test_this*hello*"])
         result = pytester.runpytest(p, "--runxfail")
         result.stdout.fnmatch_lines(
             """
@@ -543,7 +546,7 @@ def test_this(arg):
         """
         )
         result = pytester.runpytest(p, "-rxX")
-        result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*NOTRUN*"])
+        result.stdout.fnmatch_lines(["*XFAIL*test_this*NOTRUN*"])
 
     def test_dynamic_xfail_set_during_funcarg_setup(self, pytester: Pytester) -> None:
         p = pytester.makepyfile(
@@ -599,13 +602,12 @@ def test_xfail_raises(
         self, expected, actual, matchline, pytester: Pytester
     ) -> None:
         p = pytester.makepyfile(
-            """
+            f"""
             import pytest
-            @pytest.mark.xfail(raises=%s)
+            @pytest.mark.xfail(raises={expected})
             def test_raises():
-                raise %s()
+                raise {actual}()
         """
-            % (expected, actual)
         )
         result = pytester.runpytest(p)
         result.stdout.fnmatch_lines([matchline])
@@ -622,20 +624,20 @@ def test_foo():
         """
         )
         result = pytester.runpytest(p, "-rxX")
-        result.stdout.fnmatch_lines(["*XFAIL*", "*unsupported feature*"])
+        result.stdout.fnmatch_lines(["*XFAIL*unsupported feature*"])
         assert result.ret == 0
 
     @pytest.mark.parametrize("strict", [True, False])
     def test_strict_xfail(self, pytester: Pytester, strict: bool) -> None:
         p = pytester.makepyfile(
-            """
+            f"""
             import pytest
 
-            @pytest.mark.xfail(reason='unsupported feature', strict=%s)
+            @pytest.mark.xfail(reason='unsupported feature', strict={strict})
             def test_foo():
-                with open('foo_executed', 'w'): pass  # make sure test executes
+                with open('foo_executed', 'w', encoding='utf-8'):
+                    pass  # make sure test executes
         """
-            % strict
         )
         result = pytester.runpytest(p, "-rxX")
         if strict:
@@ -646,7 +648,7 @@ def test_foo():
             result.stdout.fnmatch_lines(
                 [
                     "*test_strict_xfail*",
-                    "XPASS test_strict_xfail.py::test_foo unsupported feature",
+                    "XPASS test_strict_xfail.py::test_foo - unsupported feature",
                 ]
             )
         assert result.ret == (1 if strict else 0)
@@ -655,14 +657,13 @@ def test_foo():
     @pytest.mark.parametrize("strict", [True, False])
     def test_strict_xfail_condition(self, pytester: Pytester, strict: bool) -> None:
         p = pytester.makepyfile(
-            """
+            f"""
             import pytest
 
-            @pytest.mark.xfail(False, reason='unsupported feature', strict=%s)
+            @pytest.mark.xfail(False, reason='unsupported feature', strict={strict})
             def test_foo():
                 pass
         """
-            % strict
         )
         result = pytester.runpytest(p, "-rxX")
         result.stdout.fnmatch_lines(["*1 passed*"])
@@ -671,14 +672,13 @@ def test_foo():
     @pytest.mark.parametrize("strict", [True, False])
     def test_xfail_condition_keyword(self, pytester: Pytester, strict: bool) -> None:
         p = pytester.makepyfile(
-            """
+            f"""
             import pytest
 
-            @pytest.mark.xfail(condition=False, reason='unsupported feature', strict=%s)
+            @pytest.mark.xfail(condition=False, reason='unsupported feature', strict={strict})
             def test_foo():
                 pass
         """
-            % strict
         )
         result = pytester.runpytest(p, "-rxX")
         result.stdout.fnmatch_lines(["*1 passed*"])
@@ -689,11 +689,10 @@ def test_strict_xfail_default_from_file(
         self, pytester: Pytester, strict_val
     ) -> None:
         pytester.makeini(
-            """
+            f"""
             [pytest]
-            xfail_strict = %s
+            xfail_strict = {strict_val}
         """
-            % strict_val
         )
         p = pytester.makepyfile(
             """
@@ -900,13 +899,12 @@ def test_func():
     )
     def test_skipif_reporting(self, pytester: Pytester, params) -> None:
         p = pytester.makepyfile(
-            test_foo="""
+            test_foo=f"""
             import pytest
-            @pytest.mark.skipif(%(params)s)
+            @pytest.mark.skipif({params})
             def test_that():
                 assert 0
         """
-            % dict(params=params)
         )
         result = pytester.runpytest(p, "-s", "-rs")
         result.stdout.fnmatch_lines(["*SKIP*1*test_foo.py*platform*", "*1 skipped*"])
@@ -931,15 +929,13 @@ def test_skipif_reporting_multiple(
         self, pytester: Pytester, marker, msg1, msg2
     ) -> None:
         pytester.makepyfile(
-            test_foo="""
+            test_foo=f"""
             import pytest
             @pytest.mark.{marker}(False, reason='first_condition')
             @pytest.mark.{marker}(True, reason='second_condition')
             def test_foobar():
                 assert 1
-        """.format(
-                marker=marker
-            )
+        """
         )
         result = pytester.runpytest("-s", "-rsxX")
         result.stdout.fnmatch_lines(
@@ -986,33 +982,34 @@ def test_skipped_reasons_functional(pytester: Pytester) -> None:
     pytester.makepyfile(
         test_one="""
             import pytest
-            from conftest import doskip
+            from helpers import doskip
 
-            def setup_function(func):
-                doskip()
+            def setup_function(func):  # LINE 4
+                doskip("setup function")
 
             def test_func():
                 pass
 
-            class TestClass(object):
+            class TestClass:
                 def test_method(self):
-                    doskip()
+                    doskip("test method")
 
-                @pytest.mark.skip("via_decorator")
+                @pytest.mark.skip("via_decorator")  # LINE 14
                 def test_deco(self):
                     assert 0
         """,
-        conftest="""
+        helpers="""
             import pytest, sys
-            def doskip():
+            def doskip(reason):
                 assert sys._getframe().f_lineno == 3
-                pytest.skip('test')
+                pytest.skip(reason)  # LINE 4
         """,
     )
     result = pytester.runpytest("-rs")
     result.stdout.fnmatch_lines_random(
         [
-            "SKIPPED [[]2[]] conftest.py:4: test",
+            "SKIPPED [[]1[]] test_one.py:7: setup function",
+            "SKIPPED [[]1[]] helpers.py:4: test method",
             "SKIPPED [[]1[]] test_one.py:14: via_decorator",
         ]
     )
@@ -1139,16 +1136,12 @@ def test_func():
     """
     )
     result = pytester.runpytest()
-    markline = "                ^"
+    markline = "            ^"
     pypy_version_info = getattr(sys, "pypy_version_info", None)
-    if pypy_version_info is not None and pypy_version_info < (6,):
-        markline = markline[5:]
-    elif sys.version_info[:2] >= (3, 10):
-        markline = markline[11:]
-    elif sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"):
-        markline = markline[4:]
-
-    if sys.version_info[:2] >= (3, 10):
+    if pypy_version_info is not None:
+        markline = markline[7:]
+
+    if sys.version_info >= (3, 10):
         expected = [
             "*ERROR*test_nameerror*",
             "*asd*",
@@ -1187,7 +1180,7 @@ def test_boolean():
     """
     )
     result = pytester.runpytest("-rsx")
-    result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*", "*x == 3*"])
+    result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*x == 3*"])
 
 
 def test_default_markers(pytester: Pytester) -> None:
@@ -1299,8 +1292,7 @@ def test_func():
         result = pytester.runpytest("-rxs")
         result.stdout.fnmatch_lines(
             """
-            *XFAIL*
-            *True123*
+            *XFAIL*True123*
             *1 xfail*
         """
         )
@@ -1446,6 +1438,27 @@ def test_pass():
     )
 
 
+def test_skip_from_fixture(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        **{
+            "tests/test_1.py": """
+        import pytest
+        def test_pass(arg):
+            pass
+        @pytest.fixture
+        def arg():
+            condition = True
+            if condition:
+                pytest.skip("Fixture conditional skip")
+            """,
+        }
+    )
+    result = pytester.runpytest("-rs", "tests/test_1.py", "--rootdir=tests")
+    result.stdout.fnmatch_lines(
+        ["SKIPPED [[]1[]] tests/test_1.py:2: Fixture conditional skip"]
+    )
+
+
 def test_skip_using_reason_works_ok(pytester: Pytester) -> None:
     p = pytester.makepyfile(
         """
@@ -1474,54 +1487,6 @@ def test_failing_reason():
     result.assert_outcomes(failed=1)
 
 
-def test_fail_fails_with_msg_and_reason(pytester: Pytester) -> None:
-    p = pytester.makepyfile(
-        """
-        import pytest
-
-        def test_fail_both_arguments():
-            pytest.fail(reason="foo", msg="bar")
-        """
-    )
-    result = pytester.runpytest(p)
-    result.stdout.fnmatch_lines(
-        "*UsageError: Passing both ``reason`` and ``msg`` to pytest.fail(...) is not permitted.*"
-    )
-    result.assert_outcomes(failed=1)
-
-
-def test_skip_fails_with_msg_and_reason(pytester: Pytester) -> None:
-    p = pytester.makepyfile(
-        """
-        import pytest
-
-        def test_skip_both_arguments():
-            pytest.skip(reason="foo", msg="bar")
-        """
-    )
-    result = pytester.runpytest(p)
-    result.stdout.fnmatch_lines(
-        "*UsageError: Passing both ``reason`` and ``msg`` to pytest.skip(...) is not permitted.*"
-    )
-    result.assert_outcomes(failed=1)
-
-
-def test_exit_with_msg_and_reason_fails(pytester: Pytester) -> None:
-    p = pytester.makepyfile(
-        """
-        import pytest
-
-        def test_exit_both_arguments():
-            pytest.exit(reason="foo", msg="bar")
-        """
-    )
-    result = pytester.runpytest(p)
-    result.stdout.fnmatch_lines(
-        "*UsageError: cannot pass reason and msg to exit(), `msg` is deprecated, use `reason`.*"
-    )
-    result.assert_outcomes(failed=1)
-
-
 def test_exit_with_reason_works_ok(pytester: Pytester) -> None:
     p = pytester.makepyfile(
         """
diff --git a/testing/test_stash.py b/testing/test_stash.py
index bb294f5da35..c7f6f4f95fe 100644
--- a/testing/test_stash.py
+++ b/testing/test_stash.py
@@ -1,6 +1,8 @@
-import pytest
+from __future__ import annotations
+
 from _pytest.stash import Stash
 from _pytest.stash import StashKey
+import pytest
 
 
 def test_stash() -> None:
@@ -56,7 +58,7 @@ def test_stash() -> None:
     with pytest.raises(AttributeError):
         stash.foo = "nope"  # type: ignore[attr-defined]
 
-    # No interaction with anoter stash.
+    # No interaction with another stash.
     stash2 = Stash()
     key3 = StashKey[int]()
     assert key2 not in stash2
diff --git a/testing/test_stepwise.py b/testing/test_stepwise.py
index 63d29d6241f..d2ad3bae500 100644
--- a/testing/test_stepwise.py
+++ b/testing/test_stepwise.py
@@ -1,6 +1,15 @@
-import pytest
+# mypy: disallow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Sequence
+import json
+from pathlib import Path
+
+from _pytest.cacheprovider import Cache
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
+from _pytest.stepwise import STEPWISE_CACHE_DIR
+import pytest
 
 
 @pytest.fixture
@@ -77,7 +86,7 @@ def broken_pytester(pytester: Pytester) -> Pytester:
     return pytester
 
 
-def _strip_resource_warnings(lines):
+def _strip_resource_warnings(lines: Sequence[str]) -> Sequence[str]:
     # Strip unreliable ResourceWarnings, so no-output assertions on stderr can work.
     # (https://github.com/pytest-dev/pytest/issues/5088)
     return [
@@ -107,7 +116,10 @@ def test_data(expected):
     result.stdout.fnmatch_lines(["stepwise: no previously failed tests, not skipping."])
     result = pytester.runpytest("-v", "--stepwise")
     result.stdout.fnmatch_lines(
-        ["stepwise: skipping 4 already passed items.", "*1 failed, 4 deselected*"]
+        [
+            "stepwise: skipping 4 already passed items (cache from * ago, use --sw-reset to discard).",
+            "*1 failed, 4 deselected*",
+        ]
     )
 
 
@@ -277,4 +289,257 @@ def test_three():
 
 def test_sw_skip_help(pytester: Pytester) -> None:
     result = pytester.runpytest("-h")
-    result.stdout.fnmatch_lines("*implicitly enables --stepwise.")
+    result.stdout.fnmatch_lines("*Implicitly enables --stepwise.")
+
+
+def test_stepwise_xdist_dont_store_lastfailed(pytester: Pytester) -> None:
+    pytester.makefile(
+        ext=".ini",
+        pytest=f"[pytest]\ncache_dir = {pytester.path}\n",
+    )
+
+    pytester.makepyfile(
+        conftest="""
+import pytest
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_configure(config) -> None:
+    config.workerinput = True
+"""
+    )
+    pytester.makepyfile(
+        test_one="""
+def test_one():
+    assert False
+"""
+    )
+    result = pytester.runpytest("--stepwise")
+    assert result.ret == pytest.ExitCode.INTERRUPTED
+
+    stepwise_cache_file = (
+        pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR
+    )
+    assert not Path(stepwise_cache_file).exists()
+
+
+def test_disabled_stepwise_xdist_dont_clear_cache(pytester: Pytester) -> None:
+    pytester.makefile(
+        ext=".ini",
+        pytest=f"[pytest]\ncache_dir = {pytester.path}\n",
+    )
+
+    stepwise_cache_file = (
+        pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR
+    )
+    stepwise_cache_dir = stepwise_cache_file.parent
+    stepwise_cache_dir.mkdir(exist_ok=True, parents=True)
+
+    stepwise_cache_file_relative = f"{Cache._CACHE_PREFIX_VALUES}/{STEPWISE_CACHE_DIR}"
+
+    expected_value = '"test_one.py::test_one"'
+    content = {f"{stepwise_cache_file_relative}": expected_value}
+
+    pytester.makefile(ext="", **content)
+
+    pytester.makepyfile(
+        conftest="""
+import pytest
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_configure(config) -> None:
+    config.workerinput = True
+"""
+    )
+    pytester.makepyfile(
+        test_one="""
+def test_one():
+    assert True
+"""
+    )
+    result = pytester.runpytest()
+    assert result.ret == 0
+
+    assert Path(stepwise_cache_file).exists()
+    with stepwise_cache_file.open(encoding="utf-8") as file_handle:
+        observed_value = file_handle.readlines()
+    assert [expected_value] == observed_value
+
+
+def test_do_not_reset_cache_if_disabled(pytester: Pytester) -> None:
+    """
+    If pytest is run without --stepwise, do not clear the stepwise cache.
+
+    Keeping the cache around is important for this workflow:
+
+    1. Run tests with --stepwise
+    2. Stop at the failing test, and iterate over it changing the code and running it in isolation
+    (in the IDE for example).
+    3. Run tests with --stepwise again - at this point we expect to start from the failing test, which should now pass,
+       and continue with the next tests.
+    """
+    pytester.makepyfile(
+        """
+        def test_1(): pass
+        def test_2(): assert False
+        def test_3(): pass
+        """
+    )
+    result = pytester.runpytest("--stepwise")
+    result.stdout.fnmatch_lines(
+        [
+            "*::test_2 - assert False*",
+            "*failed, continuing from this test next run*",
+            "=* 1 failed, 1 passed in *",
+        ]
+    )
+
+    # Run a specific test without passing `--stepwise`.
+    result = pytester.runpytest("-k", "test_1")
+    result.stdout.fnmatch_lines(["*1 passed*"])
+
+    # Running with `--stepwise` should continue from the last failing test.
+    result = pytester.runpytest("--stepwise")
+    result.stdout.fnmatch_lines(
+        [
+            "stepwise: skipping 1 already passed items (cache from *, use --sw-reset to discard).",
+            "*::test_2 - assert False*",
+            "*failed, continuing from this test next run*",
+            "=* 1 failed, 1 deselected in *",
+        ]
+    )
+
+
+def test_reset(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+        def test_1(): pass
+        def test_2(): assert False
+        def test_3(): pass
+        """
+    )
+    result = pytester.runpytest("--stepwise", "-v")
+    result.stdout.fnmatch_lines(
+        [
+            "stepwise: no previously failed tests, not skipping.",
+            "*::test_1 *PASSED*",
+            "*::test_2 *FAILED*",
+            "*failed, continuing from this test next run*",
+            "* 1 failed, 1 passed in *",
+        ]
+    )
+
+    result = pytester.runpytest("--stepwise", "-v")
+    result.stdout.fnmatch_lines(
+        [
+            "stepwise: skipping 1 already passed items (cache from *, use --sw-reset to discard).",
+            "*::test_2 *FAILED*",
+            "*failed, continuing from this test next run*",
+            "* 1 failed, 1 deselected in *",
+        ]
+    )
+
+    # Running with --stepwise-reset restarts the stepwise workflow.
+    result = pytester.runpytest("-v", "--stepwise-reset")
+    result.stdout.fnmatch_lines(
+        [
+            "stepwise: resetting state, not skipping.",
+            "*::test_1 *PASSED*",
+            "*::test_2 *FAILED*",
+            "*failed, continuing from this test next run*",
+            "* 1 failed, 1 passed in *",
+        ]
+    )
+
+
+def test_change_test_count(pytester: Pytester) -> None:
+    # Run initially with 3 tests.
+    pytester.makepyfile(
+        """
+        def test_1(): pass
+        def test_2(): assert False
+        def test_3(): pass
+        """
+    )
+    result = pytester.runpytest("--stepwise", "-v")
+    result.stdout.fnmatch_lines(
+        [
+            "stepwise: no previously failed tests, not skipping.",
+            "*::test_1 *PASSED*",
+            "*::test_2 *FAILED*",
+            "*failed, continuing from this test next run*",
+            "* 1 failed, 1 passed in *",
+        ]
+    )
+
+    # Change the number of tests, which invalidates the test cache.
+    pytester.makepyfile(
+        """
+        def test_1(): pass
+        def test_2(): assert False
+        def test_3(): pass
+        def test_4(): pass
+        """
+    )
+    result = pytester.runpytest("--stepwise", "-v")
+    result.stdout.fnmatch_lines(
+        [
+            "stepwise: test count changed, not skipping (now 4 tests, previously 3).",
+            "*::test_1 *PASSED*",
+            "*::test_2 *FAILED*",
+            "*failed, continuing from this test next run*",
+            "* 1 failed, 1 passed in *",
+        ]
+    )
+
+    # Fix the failing test and run again.
+    pytester.makepyfile(
+        """
+        def test_1(): pass
+        def test_2(): pass
+        def test_3(): pass
+        def test_4(): pass
+        """
+    )
+    result = pytester.runpytest("--stepwise", "-v")
+    result.stdout.fnmatch_lines(
+        [
+            "stepwise: skipping 1 already passed items (cache from *, use --sw-reset to discard).",
+            "*::test_2 *PASSED*",
+            "*::test_3 *PASSED*",
+            "*::test_4 *PASSED*",
+            "* 3 passed, 1 deselected in *",
+        ]
+    )
+
+
+def test_cache_error(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+        def test_1(): pass
+        """
+    )
+    # Run stepwise normally to generate the cache information.
+    result = pytester.runpytest("--stepwise", "-v")
+    result.stdout.fnmatch_lines(
+        [
+            "stepwise: no previously failed tests, not skipping.",
+            "*::test_1 *PASSED*",
+            "* 1 passed in *",
+        ]
+    )
+
+    # Corrupt the cache.
+    cache_file = pytester.path / f".pytest_cache/v/{STEPWISE_CACHE_DIR}"
+    assert cache_file.is_file()
+    cache_file.write_text(json.dumps({"invalid": True}), encoding="UTF-8")
+
+    # Check we run as if the cache did not exist, but also show an error message.
+    result = pytester.runpytest("--stepwise", "-v")
+    result.stdout.fnmatch_lines(
+        [
+            "stepwise: error reading cache, discarding (KeyError: *",
+            "stepwise: no previously failed tests, not skipping.",
+            "*::test_1 *PASSED*",
+            "* 1 passed in *",
+        ]
+    )
diff --git a/testing/test_terminal.py b/testing/test_terminal.py
index 23f597e3325..86feb33b3ec 100644
--- a/testing/test_terminal.py
+++ b/testing/test_terminal.py
@@ -1,22 +1,21 @@
+# mypy: allow-untyped-defs
 """Terminal reporting of the full testing process."""
-import collections
+
+from __future__ import annotations
+
+from io import StringIO
 import os
+from pathlib import Path
 import sys
 import textwrap
-from io import StringIO
-from pathlib import Path
 from types import SimpleNamespace
 from typing import cast
-from typing import Dict
-from typing import List
-from typing import Tuple
+from typing import NamedTuple
 
 import pluggy
 
-import _pytest.config
-import _pytest.terminal
-import pytest
 from _pytest._io.wcwidth import wcswidth
+import _pytest.config
 from _pytest.config import Config
 from _pytest.config import ExitCode
 from _pytest.monkeypatch import MonkeyPatch
@@ -24,6 +23,7 @@
 from _pytest.reports import BaseReport
 from _pytest.reports import CollectReport
 from _pytest.reports import TestReport
+import _pytest.terminal
 from _pytest.terminal import _folded_skips
 from _pytest.terminal import _format_trimmed
 from _pytest.terminal import _get_line_with_reprcrash_message
@@ -31,8 +31,12 @@
 from _pytest.terminal import _plugin_nameversions
 from _pytest.terminal import getreportopt
 from _pytest.terminal import TerminalReporter
+import pytest
+
 
-DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"])
+class DistInfo(NamedTuple):
+    project_name: str
+    version: int
 
 
 TRANS_FNMATCH = str.maketrans({"[": "[[]", "]": "[]]"})
@@ -45,7 +49,7 @@ def __init__(self, verbosity=0):
     @property
     def args(self):
         values = []
-        values.append("--verbosity=%d" % self.verbosity)
+        values.append(f"--verbosity={self.verbosity}")
         return values
 
 
@@ -155,7 +159,6 @@ def test_report_collect_after_half_a_second(
         self, pytester: Pytester, monkeypatch: MonkeyPatch
     ) -> None:
         """Test for "collecting" being updated after 0.5s"""
-
         pytester.makepyfile(
             **{
                 "test1.py": """
@@ -244,7 +247,8 @@ class TestClass(object):
                     def test_method(self):
                         pass
                 """
-            )
+            ),
+            encoding="utf-8",
         )
         result = pytester.runpytest("-vv")
         assert result.ret == 0
@@ -322,16 +326,17 @@ def test_rewrite(self, pytester: Pytester, monkeypatch) -> None:
         tr.rewrite("hey", erase=True)
         assert f.getvalue() == "hello" + "\r" + "hey" + (6 * " ")
 
+    @pytest.mark.parametrize("category", ["foo", "failed", "error", "passed"])
     def test_report_teststatus_explicit_markup(
-        self, monkeypatch: MonkeyPatch, pytester: Pytester, color_mapping
+        self, monkeypatch: MonkeyPatch, pytester: Pytester, color_mapping, category: str
     ) -> None:
         """Test that TerminalReporter handles markup explicitly provided by
         a pytest_report_teststatus hook."""
         monkeypatch.setenv("PY_COLORS", "1")
         pytester.makeconftest(
-            """
+            f"""
             def pytest_report_teststatus(report):
-                return 'foo', 'F', ('FOO', {'red': True})
+                return {category!r}, 'F', ('FOO', {{'red': True}})
         """
         )
         pytester.makepyfile(
@@ -340,7 +345,9 @@ def test_foobar():
                 pass
         """
         )
+
         result = pytester.runpytest("-v")
+        assert not result.stderr.lines
         result.stdout.fnmatch_lines(
             color_mapping.format_for_fnmatch(["*{red}FOO{reset}*"])
         )
@@ -385,21 +392,53 @@ def test_9():
 
             def test_10():
                 pytest.xfail("It's 🕙 o'clock")
+
+            @pytest.mark.skip(
+                reason="1 cannot do foobar because baz is missing due to I don't know what"
+            )
+            def test_long_skip():
+                pass
+
+            @pytest.mark.xfail(
+                reason="2 cannot do foobar because baz is missing due to I don't know what"
+            )
+            def test_long_xfail():
+                print(1 / 0)
         """
         )
+
+        common_output = [
+            "test_verbose_skip_reason.py::test_1 SKIPPED (123) *",
+            "test_verbose_skip_reason.py::test_2 XPASS (456) *",
+            "test_verbose_skip_reason.py::test_3 XFAIL (789) *",
+            "test_verbose_skip_reason.py::test_4 XFAIL  *",
+            "test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *",
+            "test_verbose_skip_reason.py::test_6 XPASS  *",
+            "test_verbose_skip_reason.py::test_7 SKIPPED  *",
+            "test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *",
+            "test_verbose_skip_reason.py::test_9 XFAIL  *",
+            "test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *",
+        ]
+
         result = pytester.runpytest("-v")
         result.stdout.fnmatch_lines(
             [
-                "test_verbose_skip_reason.py::test_1 SKIPPED (123) *",
-                "test_verbose_skip_reason.py::test_2 XPASS (456) *",
-                "test_verbose_skip_reason.py::test_3 XFAIL (789) *",
-                "test_verbose_skip_reason.py::test_4 XFAIL  *",
-                "test_verbose_skip_reason.py::test_5 SKIPPED (unconditional skip) *",
-                "test_verbose_skip_reason.py::test_6 XPASS  *",
-                "test_verbose_skip_reason.py::test_7 SKIPPED  *",
-                "test_verbose_skip_reason.py::test_8 SKIPPED (888 is great) *",
-                "test_verbose_skip_reason.py::test_9 XFAIL  *",
-                "test_verbose_skip_reason.py::test_10 XFAIL (It's 🕙 o'clock) *",
+                *common_output,
+                "test_verbose_skip_reason.py::test_long_skip SKIPPED (1 cannot *...) *",
+                "test_verbose_skip_reason.py::test_long_xfail XFAIL (2 cannot *...) *",
+            ]
+        )
+
+        result = pytester.runpytest("-vv")
+        result.stdout.fnmatch_lines(
+            [
+                *common_output,
+                "test_verbose_skip_reason.py::test_long_skip SKIPPED"
+                " (1 cannot do foobar",
+                "because baz is missing due to I don't know what) *",
+                "test_verbose_skip_reason.py::test_long_xfail XFAIL"
+                " (2 cannot do foobar",
+                "because baz is missing due to I don't know what) *",
             ]
         )
 
@@ -414,7 +453,11 @@ def test_func():
         )
         result = pytester.runpytest("--collect-only")
         result.stdout.fnmatch_lines(
-            ["<Module test_collectonly_basic.py>", "  <Function test_func>"]
+            [
+                "<Dir test_collectonly_basic0>",
+                "  <Module test_collectonly_basic.py>",
+                "    <Function test_func>",
+            ]
         )
 
     def test_collectonly_skipped_module(self, pytester: Pytester) -> None:
@@ -443,14 +486,15 @@ def test_with_description():
         result = pytester.runpytest("--collect-only", "--verbose")
         result.stdout.fnmatch_lines(
             [
-                "<YamlFile test1.yaml>",
-                "  <YamlItem test1.yaml>",
-                "<Module test_collectonly_displays_test_description.py>",
-                "  <Function test_with_description>",
-                "    This test has a description.",
-                "    ",
-                "    more1.",
-                "      more2.",
+                "<Dir test_collectonly_displays_test_description0>",
+                "  <YamlFile test1.yaml>",
+                "    <YamlItem test1.yaml>",
+                "  <Module test_collectonly_displays_test_description.py>",
+                "    <Function test_with_description>",
+                "      This test has a description.",
+                "      ",
+                "      more1.",
+                "        more2.",
             ],
             consecutive=True,
         )
@@ -682,20 +726,18 @@ def test_three():
                     pass
            """
         )
-        result = pytester.runpytest(
-            "-Wignore::pytest.PytestRemovedIn7Warning", "-k", "test_two:", testpath
-        )
+        result = pytester.runpytest("-k", "test_t", testpath)
         result.stdout.fnmatch_lines(
             ["collected 3 items / 1 deselected / 2 selected", "*test_deselected.py ..*"]
         )
         assert result.ret == 0
 
-    def test_deselected_with_hookwrapper(self, pytester: Pytester) -> None:
+    def test_deselected_with_hook_wrapper(self, pytester: Pytester) -> None:
         pytester.makeconftest(
             """
             import pytest
 
-            @pytest.hookimpl(hookwrapper=True)
+            @pytest.hookimpl(wrapper=True)
             def pytest_collection_modifyitems(config, items):
                 yield
                 deselected = items.pop()
@@ -751,6 +793,33 @@ def test_pass():
         result.stdout.no_fnmatch_line("*= 1 deselected =*")
         assert result.ret == 0
 
+    def test_selected_count_with_error(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            test_selected_count_3="""
+                def test_one():
+                    pass
+                def test_two():
+                    pass
+                def test_three():
+                    pass
+            """,
+            test_selected_count_error="""
+                5/0
+                def test_foo():
+                    pass
+                def test_bar():
+                    pass
+            """,
+        )
+        result = pytester.runpytest("-k", "test_t")
+        result.stdout.fnmatch_lines(
+            [
+                "collected 3 items / 1 error / 1 deselected / 2 selected",
+                "* ERROR collecting test_selected_count_error.py *",
+            ]
+        )
+        assert result.ret == ExitCode.INTERRUPTED
+
     def test_no_skip_summary_if_failure(self, pytester: Pytester) -> None:
         pytester.makepyfile(
             """
@@ -790,6 +859,7 @@ def test_header_trailer_info(
         self, monkeypatch: MonkeyPatch, pytester: Pytester, request
     ) -> None:
         monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
+        monkeypatch.delenv("PYTEST_PLUGINS", raising=False)
         pytester.makepyfile(
             """
             def test_passes():
@@ -801,13 +871,7 @@ def test_passes():
         result.stdout.fnmatch_lines(
             [
                 "*===== test session starts ====*",
-                "platform %s -- Python %s*pytest-%s**pluggy-%s"
-                % (
-                    sys.platform,
-                    verinfo,
-                    pytest.__version__,
-                    pluggy.__version__,
-                ),
+                f"platform {sys.platform} -- Python {verinfo}*pytest-{pytest.__version__}**pluggy-{pluggy.__version__}",
                 "*test_header_trailer_info.py .*",
                 "=* 1 passed*in *.[0-9][0-9]s *=",
             ]
@@ -828,13 +892,7 @@ def test_passes():
         result = pytester.runpytest("--no-header")
         verinfo = ".".join(map(str, sys.version_info[:3]))
         result.stdout.no_fnmatch_line(
-            "platform %s -- Python %s*pytest-%s**pluggy-%s"
-            % (
-                sys.platform,
-                verinfo,
-                pytest.__version__,
-                pluggy.__version__,
-            )
+            f"platform {sys.platform} -- Python {verinfo}*pytest-{pytest.__version__}**pluggy-{pluggy.__version__}"
         )
         if request.config.pluginmanager.list_plugin_distinfo():
             result.stdout.no_fnmatch_line("plugins: *")
@@ -850,7 +908,7 @@ def test_header(self, pytester: Pytester) -> None:
         # with configfile
         pytester.makeini("""[pytest]""")
         result = pytester.runpytest()
-        result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"])
+        result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"])
 
         # with testpaths option, and not passing anything in the command-line
         pytester.makeini(
@@ -861,33 +919,31 @@ def test_header(self, pytester: Pytester) -> None:
         )
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(
-            ["rootdir: *test_header0, configfile: tox.ini, testpaths: tests, gui"]
+            ["rootdir: *test_header0", "configfile: tox.ini", "testpaths: tests, gui"]
         )
 
         # with testpaths option, passing directory in command-line: do not show testpaths then
         result = pytester.runpytest("tests")
-        result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"])
+        result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"])
 
     def test_header_absolute_testpath(
         self, pytester: Pytester, monkeypatch: MonkeyPatch
     ) -> None:
-        """Regresstion test for #7814."""
+        """Regression test for #7814."""
         tests = pytester.path.joinpath("tests")
         tests.mkdir()
         pytester.makepyprojecttoml(
-            """
+            f"""
             [tool.pytest.ini_options]
-            testpaths = ['{}']
-        """.format(
-                tests
-            )
+            testpaths = ['{tests}']
+        """
         )
         result = pytester.runpytest()
         result.stdout.fnmatch_lines(
             [
-                "rootdir: *absolute_testpath0, configfile: pyproject.toml, testpaths: {}".format(
-                    tests
-                )
+                "rootdir: *absolute_testpath0",
+                "configfile: pyproject.toml",
+                f"testpaths: {tests}",
             ]
         )
 
@@ -939,6 +995,22 @@ def test_showlocals():
             ]
         )
 
+    def test_noshowlocals_addopts_override(self, pytester: Pytester) -> None:
+        pytester.makeini("[pytest]\naddopts=--showlocals")
+        p1 = pytester.makepyfile(
+            """
+            def test_noshowlocals():
+                x = 3
+                y = "x" * 5000
+                assert 0
+        """
+        )
+
+        # Override global --showlocals for py.test via arg
+        result = pytester.runpytest(p1, "--no-showlocals")
+        result.stdout.no_fnmatch_line("x* = 3")
+        result.stdout.no_fnmatch_line("y* = 'xxxxxx*")
+
     def test_showlocals_short(self, pytester: Pytester) -> None:
         p1 = pytester.makepyfile(
             """
@@ -971,10 +1043,6 @@ def test_pass():
             class TestClass(object):
                 def test_skip(self):
                     pytest.skip("hello")
-            def test_gen():
-                def check(x):
-                    assert x == 1
-                yield check, 0
         """
         )
 
@@ -987,7 +1055,6 @@ def test_verbose_reporting(self, verbose_testfile, pytester: Pytester) -> None:
                 "*test_verbose_reporting.py::test_fail *FAIL*",
                 "*test_verbose_reporting.py::test_pass *PASS*",
                 "*test_verbose_reporting.py::TestClass::test_skip *SKIP*",
-                "*test_verbose_reporting.py::test_gen *XFAIL*",
             ]
         )
         assert result.ret == 1
@@ -1079,8 +1146,60 @@ def test():
         result.stdout.fnmatch_lines([expected])
         assert result.stdout.lines.count(expected) == 1
 
+    def test_summary_s_folded(self, pytester: Pytester) -> None:
+        """Test that skipped tests are correctly folded"""
+        pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize("param", [True, False])
+            @pytest.mark.skip("Some reason")
+            def test(param):
+                pass
+            """
+        )
+        result = pytester.runpytest("-rs")
+        expected = "SKIPPED [2] test_summary_s_folded.py:3: Some reason"
+        result.stdout.fnmatch_lines([expected])
+        assert result.stdout.lines.count(expected) == 1
+
+    def test_summary_s_unfolded(self, pytester: Pytester) -> None:
+        """Test that skipped tests are not folded if --no-fold-skipped is set"""
+        pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.mark.parametrize("param", [True, False])
+            @pytest.mark.skip("Some reason")
+            def test(param):
+                pass
+            """
+        )
+        result = pytester.runpytest("-rs", "--no-fold-skipped")
+        expected = [
+            "SKIPPED test_summary_s_unfolded.py::test[True] - Skipped: Some reason",
+            "SKIPPED test_summary_s_unfolded.py::test[False] - Skipped: Some reason",
+        ]
+        result.stdout.fnmatch_lines(expected)
+        assert result.stdout.lines.count(expected[0]) == 1
+        assert result.stdout.lines.count(expected[1]) == 1
+
 
-def test_fail_extra_reporting(pytester: Pytester, monkeypatch) -> None:
+@pytest.mark.parametrize(
+    ("use_ci", "expected_message"),
+    (
+        (True, f"- AssertionError: {'this_failed' * 100}"),
+        (False, "- AssertionError: this_failedt..."),
+    ),
+    ids=("on CI", "not on CI"),
+)
+def test_fail_extra_reporting(
+    pytester: Pytester, monkeypatch, use_ci: bool, expected_message: str
+) -> None:
+    if use_ci:
+        monkeypatch.setenv("CI", "true")
+    else:
+        monkeypatch.delenv("CI", raising=False)
     monkeypatch.setenv("COLUMNS", "80")
     pytester.makepyfile("def test_this(): assert 0, 'this_failed' * 100")
     result = pytester.runpytest("-rN")
@@ -1089,7 +1208,7 @@ def test_fail_extra_reporting(pytester: Pytester, monkeypatch) -> None:
     result.stdout.fnmatch_lines(
         [
             "*test summary*",
-            "FAILED test_fail_extra_reporting.py::test_this - AssertionError: this_failedt...",
+            f"FAILED test_fail_extra_reporting.py::test_this {expected_message}",
         ]
     )
 
@@ -1176,14 +1295,14 @@ def test_this():
                 "=*= FAILURES =*=",
                 "{red}{bold}_*_ test_this _*_{reset}",
                 "",
-                "    {kw}def{hl-reset} {function}test_this{hl-reset}():",
-                ">       fail()",
+                "    {reset}{kw}def{hl-reset}{kwspace}{function}test_this{hl-reset}():{endline}",
+                ">       fail(){endline}",
                 "",
                 "{bold}{red}test_color_yes.py{reset}:5: ",
                 "_ _ * _ _*",
                 "",
-                "    {kw}def{hl-reset} {function}fail{hl-reset}():",
-                ">       {kw}assert{hl-reset} {number}0{hl-reset}",
+                "    {reset}{kw}def{hl-reset}{kwspace}{function}fail{hl-reset}():{endline}",
+                ">       {kw}assert{hl-reset} {number}0{hl-reset}{endline}",
                 "{bold}{red}E       assert 0{reset}",
                 "",
                 "{bold}{red}test_color_yes.py{reset}:2: AssertionError",
@@ -1203,9 +1322,9 @@ def test_this():
                 "=*= FAILURES =*=",
                 "{red}{bold}_*_ test_this _*_{reset}",
                 "{bold}{red}test_color_yes.py{reset}:5: in test_this",
-                "    fail()",
+                "    {reset}fail(){endline}",
                 "{bold}{red}test_color_yes.py{reset}:2: in fail",
-                "    {kw}assert{hl-reset} {number}0{hl-reset}",
+                "    {reset}{kw}assert{hl-reset} {number}0{hl-reset}{endline}",
                 "{bold}{red}E   assert 0{reset}",
                 "{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}",
             ]
@@ -1338,7 +1457,7 @@ def test_opt(arg):
     s = result.stdout.str()
     assert "arg = 42" not in s
     assert "x = 0" not in s
-    result.stdout.fnmatch_lines(["*%s:8*" % p.name, "    assert x", "E   assert*"])
+    result.stdout.fnmatch_lines([f"*{p.name}:8*", "    assert x", "E   assert*"])
     result = pytester.runpytest()
     s = result.stdout.str()
     assert "x = 0" in s
@@ -1414,8 +1533,8 @@ def test_func():
         """
         )
         for tbopt in ["long", "short", "no"]:
-            print("testing --tb=%s..." % tbopt)
-            result = pytester.runpytest("-rN", "--tb=%s" % tbopt)
+            print(f"testing --tb={tbopt}...")
+            result = pytester.runpytest("-rN", f"--tb={tbopt}")
             s = result.stdout.str()
             if tbopt == "long":
                 assert "print(6*7)" in s
@@ -1445,11 +1564,24 @@ def test_func2():
         result = pytester.runpytest("--tb=line")
         bn = p.name
         result.stdout.fnmatch_lines(
-            ["*%s:3: IndexError*" % bn, "*%s:8: AssertionError: hello*" % bn]
+            [f"*{bn}:3: IndexError*", f"*{bn}:8: AssertionError: hello*"]
         )
         s = result.stdout.str()
         assert "def test_func2" not in s
 
+    def test_tb_crashline_pytrace_false(self, pytester: Pytester, option) -> None:
+        p = pytester.makepyfile(
+            """
+            import pytest
+            def test_func1():
+                pytest.fail('test_func1', pytrace=False)
+        """
+        )
+        result = pytester.runpytest("--tb=line")
+        result.stdout.str()
+        bn = p.name
+        result.stdout.fnmatch_lines([f"*{bn}:3: Failed: test_func1"])
+
     def test_pytest_report_header(self, pytester: Pytester, option) -> None:
         pytester.makeconftest(
             """
@@ -1463,7 +1595,8 @@ def pytest_report_header(config):
             """
 def pytest_report_header(config, start_path):
     return ["line1", str(start_path)]
-"""
+""",
+            encoding="utf-8",
         )
         result = pytester.runpytest("a")
         result.stdout.fnmatch_lines(["*hello: 42*", "line1", str(pytester.path)])
@@ -1567,7 +1700,7 @@ def test_fdopen_kept_alive_issue124(pytester: Pytester) -> None:
         import os, sys
         k = []
         def test_open_file_and_keep_alive(capfd):
-            stdout = os.fdopen(1, 'w', 1)
+            stdout = os.fdopen(1, 'w', buffering=1, encoding='utf-8')
             k.append(stdout)
 
         def test_close_kept_alive_file():
@@ -1696,7 +1829,7 @@ def test_failure():
 
 @pytest.fixture(scope="session")
 def tr() -> TerminalReporter:
-    config = _pytest.config._prepareconfig()
+    config = _pytest.config._prepareconfig([])
     return TerminalReporter(config)
 
 
@@ -1832,9 +1965,9 @@ def tr() -> TerminalReporter:
 )
 def test_summary_stats(
     tr: TerminalReporter,
-    exp_line: List[Tuple[str, Dict[str, bool]]],
+    exp_line: list[tuple[str, dict[str, bool]]],
     exp_color: str,
-    stats_arg: Dict[str, List[object]],
+    stats_arg: dict[str, list[object]],
 ) -> None:
     tr.stats = stats_arg
 
@@ -1848,7 +1981,7 @@ class fake_session:
     # Reset cache.
     tr._main_color = None
 
-    print("Based on stats: %s" % stats_arg)
+    print(f"Based on stats: {stats_arg}")
     print(f'Expect summary: "{exp_line}"; with color "{exp_color}"')
     (line, color) = tr.build_summary_stats_line()
     print(f'Actually got:   "{line}"; with color "{color}"')
@@ -1895,9 +2028,9 @@ def test_normal_verbosity(self, pytester: Pytester, test_files) -> None:
         result = pytester.runpytest("-o", "console_output_style=classic")
         result.stdout.fnmatch_lines(
             [
+                f"sub{os.sep}test_three.py .F.",
                 "test_one.py .",
                 "test_two.py F",
-                f"sub{os.sep}test_three.py .F.",
                 "*2 failed, 3 passed in*",
             ]
         )
@@ -1906,18 +2039,18 @@ def test_verbose(self, pytester: Pytester, test_files) -> None:
         result = pytester.runpytest("-o", "console_output_style=classic", "-v")
         result.stdout.fnmatch_lines(
             [
-                "test_one.py::test_one PASSED",
-                "test_two.py::test_two FAILED",
                 f"sub{os.sep}test_three.py::test_three_1 PASSED",
                 f"sub{os.sep}test_three.py::test_three_2 FAILED",
                 f"sub{os.sep}test_three.py::test_three_3 PASSED",
+                "test_one.py::test_one PASSED",
+                "test_two.py::test_two FAILED",
                 "*2 failed, 3 passed in*",
             ]
         )
 
     def test_quiet(self, pytester: Pytester, test_files) -> None:
         result = pytester.runpytest("-o", "console_output_style=classic", "-q")
-        result.stdout.fnmatch_lines([".F.F.", "*2 failed, 3 passed in*"])
+        result.stdout.fnmatch_lines([".F..F", "*2 failed, 3 passed in*"])
 
 
 class TestProgressOutputStyle:
@@ -1941,6 +2074,21 @@ def test_foobar(i): pass
             """,
         )
 
+    @pytest.fixture
+    def more_tests_files(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            test_bar="""
+                import pytest
+                @pytest.mark.parametrize('i', range(30))
+                def test_bar(i): pass
+            """,
+            test_foo="""
+                import pytest
+                @pytest.mark.parametrize('i', range(5))
+                def test_foo(i): pass
+            """,
+        )
+
     def test_zero_tests_collected(self, pytester: Pytester) -> None:
         """Some plugins (testmon for example) might issue pytest_runtest_logreport without any tests being
         actually collected (#2971)."""
@@ -2037,6 +2185,52 @@ def test_count(self, many_tests_files, pytester: Pytester) -> None:
             ]
         )
 
+    def test_times(self, many_tests_files, pytester: Pytester) -> None:
+        pytester.makeini(
+            """
+            [pytest]
+            console_output_style = times
+        """
+        )
+        output = pytester.runpytest()
+        output.stdout.re_match_lines(
+            [
+                r"test_bar.py \.{10} \s+ \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2}$",
+                r"test_foo.py \.{5} \s+ \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2}$",
+                r"test_foobar.py \.{5} \s+ \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2}$",
+            ]
+        )
+
+    def test_times_multiline(
+        self, more_tests_files, monkeypatch, pytester: Pytester
+    ) -> None:
+        monkeypatch.setenv("COLUMNS", "40")
+        pytester.makeini(
+            """
+            [pytest]
+            console_output_style = times
+        """
+        )
+        output = pytester.runpytest()
+        output.stdout.re_match_lines(
+            [
+                r"test_bar.py ...................",
+                r"........... \s+ \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2}$",
+                r"test_foo.py \.{5} \s+ \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2}$",
+            ],
+            consecutive=True,
+        )
+
+    def test_times_none_collected(self, pytester: Pytester) -> None:
+        pytester.makeini(
+            """
+            [pytest]
+            console_output_style = times
+        """
+        )
+        output = pytester.runpytest()
+        assert output.ret == ExitCode.NO_TESTS_COLLECTED
+
     def test_verbose(self, many_tests_files, pytester: Pytester) -> None:
         output = pytester.runpytest("-v")
         output.stdout.re_match_lines(
@@ -2063,6 +2257,22 @@ def test_verbose_count(self, many_tests_files, pytester: Pytester) -> None:
             ]
         )
 
+    def test_verbose_times(self, many_tests_files, pytester: Pytester) -> None:
+        pytester.makeini(
+            """
+            [pytest]
+            console_output_style = times
+        """
+        )
+        output = pytester.runpytest("-v")
+        output.stdout.re_match_lines(
+            [
+                r"test_bar.py::test_bar\[0\] PASSED \s+ \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2}$",
+                r"test_foo.py::test_foo\[4\] PASSED \s+ \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2}$",
+                r"test_foobar.py::test_foobar\[4\] PASSED \s+ \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2}$",
+            ]
+        )
+
     def test_xdist_normal(
         self, many_tests_files, pytester: Pytester, monkeypatch
     ) -> None:
@@ -2115,6 +2325,26 @@ def test_xdist_verbose(
             ]
         )
 
+    def test_xdist_times(
+        self, many_tests_files, pytester: Pytester, monkeypatch
+    ) -> None:
+        pytest.importorskip("xdist")
+        monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
+        pytester.makeini(
+            """
+            [pytest]
+            console_output_style = times
+        """
+        )
+        output = pytester.runpytest("-n2", "-v")
+        output.stdout.re_match_lines_random(
+            [
+                r"\[gw\d\] \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2} PASSED test_bar.py::test_bar\[1\]",
+                r"\[gw\d\] \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2} PASSED test_foo.py::test_foo\[1\]",
+                r"\[gw\d\] \d{1,3}[\.[a-z\ ]{1,2}\d{0,3}\w{1,2} PASSED test_foobar.py::test_foobar\[1\]",
+            ]
+        )
+
     def test_capture_no(self, many_tests_files, pytester: Pytester) -> None:
         output = pytester.runpytest("-s")
         output.stdout.re_match_lines(
@@ -2124,6 +2354,24 @@ def test_capture_no(self, many_tests_files, pytester: Pytester) -> None:
         output = pytester.runpytest("--capture=no")
         output.stdout.no_fnmatch_line("*%]*")
 
+    def test_capture_no_progress_enabled(
+        self, many_tests_files, pytester: Pytester
+    ) -> None:
+        pytester.makeini(
+            """
+            [pytest]
+            console_output_style = progress-even-when-capture-no
+        """
+        )
+        output = pytester.runpytest("-s")
+        output.stdout.re_match_lines(
+            [
+                r"test_bar.py \.{10} \s+ \[ 50%\]",
+                r"test_foo.py \.{5} \s+ \[ 75%\]",
+                r"test_foobar.py \.{5} \s+ \[100%\]",
+            ]
+        )
+
 
 class TestProgressWithTeardown:
     """Ensure we show the correct percentages for tests that fail during teardown (#3088)"""
@@ -2260,24 +2508,40 @@ def test_line_with_reprcrash(monkeypatch: MonkeyPatch) -> None:
     def mock_get_pos(*args):
         return mocked_pos
 
-    monkeypatch.setattr(_pytest.terminal, "_get_pos", mock_get_pos)
+    monkeypatch.setattr(_pytest.terminal, "_get_node_id_with_markup", mock_get_pos)
+
+    class Namespace:
+        def __init__(self, **kwargs):
+            self.__dict__.update(kwargs)
 
     class config:
-        pass
+        def __init__(self):
+            self.option = Namespace(verbose=0)
 
     class rep:
-        def _get_verbose_word(self, *args):
-            return mocked_verbose_word
+        def _get_verbose_word_with_markup(self, *args):
+            return mocked_verbose_word, {}
 
         class longrepr:
             class reprcrash:
                 pass
 
     def check(msg, width, expected):
+        class DummyTerminalWriter:
+            fullwidth = width
+
+            def markup(self, word: str, **markup: str):
+                return word
+
         __tracebackhide__ = True
         if msg:
             rep.longrepr.reprcrash.message = msg  # type: ignore
-        actual = _get_line_with_reprcrash_message(config, rep(), width)  # type: ignore
+        actual = _get_line_with_reprcrash_message(
+            config(),  # type: ignore[arg-type]
+            rep(),  # type: ignore[arg-type]
+            DummyTerminalWriter(),  # type: ignore[arg-type]
+            {},
+        )
 
         assert actual == expected
         if actual != f"{mocked_verbose_word} {mocked_pos}":
@@ -2317,6 +2581,89 @@ def check(msg, width, expected):
     check("🉐🉐🉐🉐🉐\n2nd line", 80, "FAILED nodeid::🉐::withunicode - 🉐🉐🉐🉐🉐")
 
 
+def test_short_summary_with_verbose(
+    monkeypatch: MonkeyPatch, pytester: Pytester
+) -> None:
+    """With -vv do not truncate the summary info (#11777)."""
+    # On CI we also do not truncate the summary info, monkeypatch it to ensure we
+    # are testing against the -vv flag on CI.
+    monkeypatch.setattr(_pytest.terminal, "running_on_ci", lambda: False)
+
+    string_length = 200
+    pytester.makepyfile(
+        f"""
+        def test():
+            s1 = "A" * {string_length}
+            s2 = "B" * {string_length}
+            assert s1 == s2
+        """
+    )
+
+    # No -vv, summary info should be truncated.
+    result = pytester.runpytest()
+    result.stdout.fnmatch_lines(
+        [
+            "*short test summary info*",
+            "* assert 'AAA...",
+        ],
+    )
+
+    # No truncation with -vv.
+    result = pytester.runpytest("-vv")
+    result.stdout.fnmatch_lines(
+        [
+            "*short test summary info*",
+            f"*{'A' * string_length}*{'B' * string_length}'",
+        ]
+    )
+
+
+def test_full_sequence_print_with_vv(
+    monkeypatch: MonkeyPatch, pytester: Pytester
+) -> None:
+    """Do not truncate sequences in summaries with -vv (#11777)."""
+    monkeypatch.setattr(_pytest.terminal, "running_on_ci", lambda: False)
+
+    pytester.makepyfile(
+        """
+        def test_len_list():
+            l = list(range(10))
+            assert len(l) == 9
+
+        def test_len_dict():
+            d = dict(zip(range(10), range(10)))
+            assert len(d) == 9
+        """
+    )
+
+    result = pytester.runpytest("-vv")
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(
+        [
+            "*short test summary info*",
+            f"*{list(range(10))}*",
+            f"*{dict(zip(range(10), range(10)))}*",
+        ]
+    )
+
+
+def test_force_short_summary(monkeypatch: MonkeyPatch, pytester: Pytester) -> None:
+    monkeypatch.setattr(_pytest.terminal, "running_on_ci", lambda: False)
+
+    pytester.makepyfile(
+        """
+        def test():
+            assert "a\\n" * 10 == ""
+        """
+    )
+
+    result = pytester.runpytest("-vv", "--force-short-summary")
+    assert result.ret == 1
+    result.stdout.fnmatch_lines(
+        ["*short test summary info*", "*AssertionError: assert 'a\\na\\na\\na..."]
+    )
+
+
 @pytest.mark.parametrize(
     "seconds, expected",
     [
@@ -2334,6 +2681,27 @@ def test_format_session_duration(seconds, expected):
     assert format_session_duration(seconds) == expected
 
 
+@pytest.mark.parametrize(
+    "seconds, expected",
+    [
+        (3600 * 100 - 60, " 99h 59m"),
+        (31 * 60 - 1, " 30m 59s"),
+        (10.1236, " 10.124s"),
+        (9.1236, " 9.124s"),
+        (0.1236, " 123.6ms"),
+        (0.01236, " 12.36ms"),
+        (0.001236, " 1.236ms"),
+        (0.0001236, " 123.6us"),
+        (0.00001236, " 12.36us"),
+        (0.000001236, " 1.236us"),
+    ],
+)
+def test_format_node_duration(seconds: float, expected: str) -> None:
+    from _pytest.terminal import format_node_duration
+
+    assert format_node_duration(seconds) == expected
+
+
 def test_collecterror(pytester: Pytester) -> None:
     p1 = pytester.makepyfile("raise SyntaxError()")
     result = pytester.runpytest("-ra", str(p1))
@@ -2377,8 +2745,8 @@ def test_foo():
         result.stdout.fnmatch_lines(
             color_mapping.format_for_fnmatch(
                 [
-                    "    {kw}def{hl-reset} {function}test_foo{hl-reset}():",
-                    ">       {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}",
+                    "    {reset}{kw}def{hl-reset}{kwspace}{function}test_foo{hl-reset}():{endline}",
+                    ">       {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}",
                     "{bold}{red}E       assert 1 == 10{reset}",
                 ]
             )
@@ -2399,9 +2767,9 @@ def test_foo():
         result.stdout.fnmatch_lines(
             color_mapping.format_for_fnmatch(
                 [
-                    "    {kw}def{hl-reset} {function}test_foo{hl-reset}():",
+                    "    {reset}{kw}def{hl-reset}{kwspace}{function}test_foo{hl-reset}():{endline}",
                     "        {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}",
-                    ">   {str}    {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}",
+                    ">   {str}    {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}{endline}",
                     "{bold}{red}E       assert 0{reset}",
                 ]
             )
@@ -2422,8 +2790,8 @@ def test_foo():
         result.stdout.fnmatch_lines(
             color_mapping.format_for_fnmatch(
                 [
-                    "    {kw}def{hl-reset} {function}test_foo{hl-reset}():",
-                    ">       {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}",
+                    "    {reset}{kw}def{hl-reset}{kwspace}{function}test_foo{hl-reset}():{endline}",
+                    ">       {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}",
                     "{bold}{red}E       assert 1 == 10{reset}",
                 ]
             )
@@ -2441,8 +2809,8 @@ def test_foo():
         monkeypatch.setenv("PYTEST_THEME", "invalid")
         result = pytester.runpytest_subprocess("--color=yes")
         result.stderr.fnmatch_lines(
-            "ERROR: PYTEST_THEME environment variable had an invalid value: 'invalid'. "
-            "Only valid pygment styles are allowed."
+            "ERROR: PYTEST_THEME environment variable has an invalid value: 'invalid'. "
+            "Hint: See available pygments styles with `pygmentize -L styles`."
         )
 
     def test_code_highlight_invalid_theme_mode(
@@ -2457,8 +2825,8 @@ def test_foo():
         monkeypatch.setenv("PYTEST_THEME_MODE", "invalid")
         result = pytester.runpytest_subprocess("--color=yes")
         result.stderr.fnmatch_lines(
-            "ERROR: PYTEST_THEME_MODE environment variable had an invalid value: 'invalid'. "
-            "The only allowed values are 'dark' and 'light'."
+            "ERROR: PYTEST_THEME_MODE environment variable has an invalid value: 'invalid'. "
+            "The allowed values are 'dark' (default) and 'light'."
         )
 
 
@@ -2484,3 +2852,413 @@ def test_format_trimmed() -> None:
 
     assert _format_trimmed(" ({}) ", msg, len(msg) + 4) == " (unconditional skip) "
     assert _format_trimmed(" ({}) ", msg, len(msg) + 3) == " (unconditional ...) "
+
+
+class TestFineGrainedTestCase:
+    DEFAULT_FILE_CONTENTS = """
+            import pytest
+
+            @pytest.mark.parametrize("i", range(4))
+            def test_ok(i):
+                '''
+                some docstring
+                '''
+                pass
+
+            def test_fail():
+                assert False
+            """
+    LONG_SKIP_FILE_CONTENTS = """
+            import pytest
+
+            @pytest.mark.skip(
+              "some long skip reason that will not fit on a single line with other content that goes"
+              " on and on and on and on and on"
+            )
+            def test_skip():
+                pass
+            """
+
+    @pytest.mark.parametrize("verbosity", [1, 2])
+    def test_execute_positive(self, verbosity, pytester: Pytester) -> None:
+        # expected: one test case per line (with file name), word describing result
+        p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=verbosity)
+        result = pytester.runpytest(p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collected 5 items",
+                "",
+                f"{p.name}::test_ok[0] PASSED                              [ 20%]",
+                f"{p.name}::test_ok[1] PASSED                              [ 40%]",
+                f"{p.name}::test_ok[2] PASSED                              [ 60%]",
+                f"{p.name}::test_ok[3] PASSED                              [ 80%]",
+                f"{p.name}::test_fail FAILED                               [100%]",
+            ],
+            consecutive=True,
+        )
+
+    def test_execute_0_global_1(self, pytester: Pytester) -> None:
+        # expected: one file name per line, single character describing result
+        p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=0)
+        result = pytester.runpytest("-v", p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collecting ... collected 5 items",
+                "",
+                f"{p.name} ....F                                         [100%]",
+            ],
+            consecutive=True,
+        )
+
+    @pytest.mark.parametrize("verbosity", [-1, -2])
+    def test_execute_negative(self, verbosity, pytester: Pytester) -> None:
+        # expected: single character describing result
+        p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=verbosity)
+        result = pytester.runpytest(p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collected 5 items",
+                "....F                                                                    [100%]",
+            ],
+            consecutive=True,
+        )
+
+    def test_execute_skipped_positive_2(self, pytester: Pytester) -> None:
+        # expected: one test case per line (with file name), word describing result, full reason
+        p = TestFineGrainedTestCase._initialize_files(
+            pytester,
+            verbosity=2,
+            file_contents=TestFineGrainedTestCase.LONG_SKIP_FILE_CONTENTS,
+        )
+        result = pytester.runpytest(p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collected 1 item",
+                "",
+                f"{p.name}::test_skip SKIPPED (some long skip",
+                "reason that will not fit on a single line with other content that goes",
+                "on and on and on and on and on)                                          [100%]",
+            ],
+            consecutive=True,
+        )
+
+    def test_execute_skipped_positive_1(self, pytester: Pytester) -> None:
+        # expected: one test case per line (with file name), word describing result, reason truncated
+        p = TestFineGrainedTestCase._initialize_files(
+            pytester,
+            verbosity=1,
+            file_contents=TestFineGrainedTestCase.LONG_SKIP_FILE_CONTENTS,
+        )
+        result = pytester.runpytest(p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collected 1 item",
+                "",
+                f"{p.name}::test_skip SKIPPED (some long ski...) [100%]",
+            ],
+            consecutive=True,
+        )
+
+    def test_execute_skipped__0_global_1(self, pytester: Pytester) -> None:
+        # expected: one file name per line, single character describing result (no reason)
+        p = TestFineGrainedTestCase._initialize_files(
+            pytester,
+            verbosity=0,
+            file_contents=TestFineGrainedTestCase.LONG_SKIP_FILE_CONTENTS,
+        )
+        result = pytester.runpytest("-v", p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collecting ... collected 1 item",
+                "",
+                f"{p.name} s                                    [100%]",
+            ],
+            consecutive=True,
+        )
+
+    @pytest.mark.parametrize("verbosity", [-1, -2])
+    def test_execute_skipped_negative(self, verbosity, pytester: Pytester) -> None:
+        # expected: single character describing result (no reason)
+        p = TestFineGrainedTestCase._initialize_files(
+            pytester,
+            verbosity=verbosity,
+            file_contents=TestFineGrainedTestCase.LONG_SKIP_FILE_CONTENTS,
+        )
+        result = pytester.runpytest(p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collected 1 item",
+                "s                                                                        [100%]",
+            ],
+            consecutive=True,
+        )
+
+    @pytest.mark.parametrize("verbosity", [1, 2])
+    def test__collect_only_positive(self, verbosity, pytester: Pytester) -> None:
+        p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=verbosity)
+        result = pytester.runpytest("--collect-only", p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collected 5 items",
+                "",
+                f"<Dir {p.parent.name}>",
+                f"  <Module {p.name}>",
+                "    <Function test_ok[0]>",
+                "      some docstring",
+                "    <Function test_ok[1]>",
+                "      some docstring",
+                "    <Function test_ok[2]>",
+                "      some docstring",
+                "    <Function test_ok[3]>",
+                "      some docstring",
+                "    <Function test_fail>",
+            ],
+            consecutive=True,
+        )
+
+    def test_collect_only_0_global_1(self, pytester: Pytester) -> None:
+        p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=0)
+        result = pytester.runpytest("-v", "--collect-only", p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collecting ... collected 5 items",
+                "",
+                f"<Dir {p.parent.name}>",
+                f"  <Module {p.name}>",
+                "    <Function test_ok[0]>",
+                "    <Function test_ok[1]>",
+                "    <Function test_ok[2]>",
+                "    <Function test_ok[3]>",
+                "    <Function test_fail>",
+            ],
+            consecutive=True,
+        )
+
+    def test_collect_only_negative_1(self, pytester: Pytester) -> None:
+        p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=-1)
+        result = pytester.runpytest("--collect-only", p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collected 5 items",
+                "",
+                f"{p.name}::test_ok[0]",
+                f"{p.name}::test_ok[1]",
+                f"{p.name}::test_ok[2]",
+                f"{p.name}::test_ok[3]",
+                f"{p.name}::test_fail",
+            ],
+            consecutive=True,
+        )
+
+    def test_collect_only_negative_2(self, pytester: Pytester) -> None:
+        p = TestFineGrainedTestCase._initialize_files(pytester, verbosity=-2)
+        result = pytester.runpytest("--collect-only", p)
+
+        result.stdout.fnmatch_lines(
+            [
+                "collected 5 items",
+                "",
+                f"{p.name}: 5",
+            ],
+            consecutive=True,
+        )
+
+    @staticmethod
+    def _initialize_files(
+        pytester: Pytester, verbosity: int, file_contents: str = DEFAULT_FILE_CONTENTS
+    ) -> Path:
+        p = pytester.makepyfile(file_contents)
+        pytester.makeini(
+            f"""
+            [pytest]
+            verbosity_test_cases = {verbosity}
+            """
+        )
+        return p
+
+
+def test_summary_xfail_reason(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.mark.xfail
+        def test_xfail():
+            assert False
+
+        @pytest.mark.xfail(reason="foo")
+        def test_xfail_reason():
+            assert False
+        """
+    )
+    result = pytester.runpytest("-rx")
+    expect1 = "XFAIL test_summary_xfail_reason.py::test_xfail"
+    expect2 = "XFAIL test_summary_xfail_reason.py::test_xfail_reason - foo"
+    result.stdout.fnmatch_lines([expect1, expect2])
+    assert result.stdout.lines.count(expect1) == 1
+    assert result.stdout.lines.count(expect2) == 1
+
+
+@pytest.fixture()
+def xfail_testfile(pytester: Pytester) -> Path:
+    return pytester.makepyfile(
+        """
+        import pytest
+
+        def test_fail():
+            a, b = 1, 2
+            assert a == b
+
+        @pytest.mark.xfail
+        def test_xfail():
+            c, d = 3, 4
+            assert c == d
+        """
+    )
+
+
+def test_xfail_tb_default(xfail_testfile, pytester: Pytester) -> None:
+    result = pytester.runpytest(xfail_testfile)
+
+    # test_fail, show traceback
+    result.stdout.fnmatch_lines(
+        [
+            "*= FAILURES =*",
+            "*_ test_fail _*",
+            "*def test_fail():*",
+            "*        a, b = 1, 2*",
+            "*>       assert a == b*",
+            "*E       assert 1 == 2*",
+        ]
+    )
+
+    # test_xfail, don't show traceback
+    result.stdout.no_fnmatch_line("*= XFAILURES =*")
+
+
+def test_xfail_tb_true(xfail_testfile, pytester: Pytester) -> None:
+    result = pytester.runpytest(xfail_testfile, "--xfail-tb")
+
+    # both test_fail and test_xfail, show traceback
+    result.stdout.fnmatch_lines(
+        [
+            "*= FAILURES =*",
+            "*_ test_fail _*",
+            "*def test_fail():*",
+            "*        a, b = 1, 2*",
+            "*>       assert a == b*",
+            "*E       assert 1 == 2*",
+            "*= XFAILURES =*",
+            "*_ test_xfail _*",
+            "*def test_xfail():*",
+            "*        c, d = 3, 4*",
+            "*>       assert c == d*",
+            "*E       assert 3 == 4*",
+            "*short test summary info*",
+        ]
+    )
+
+
+def test_xfail_tb_line(xfail_testfile, pytester: Pytester) -> None:
+    result = pytester.runpytest(xfail_testfile, "--xfail-tb", "--tb=line")
+
+    # both test_fail and test_xfail, show line
+    result.stdout.fnmatch_lines(
+        [
+            "*= FAILURES =*",
+            "*test_xfail_tb_line.py:5: assert 1 == 2",
+            "*= XFAILURES =*",
+            "*test_xfail_tb_line.py:10: assert 3 == 4",
+            "*short test summary info*",
+        ]
+    )
+
+
+def test_summary_xpass_reason(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.mark.xfail
+        def test_pass():
+            ...
+
+        @pytest.mark.xfail(reason="foo")
+        def test_reason():
+            ...
+        """
+    )
+    result = pytester.runpytest("-rX")
+    expect1 = "XPASS test_summary_xpass_reason.py::test_pass"
+    expect2 = "XPASS test_summary_xpass_reason.py::test_reason - foo"
+    result.stdout.fnmatch_lines([expect1, expect2])
+    assert result.stdout.lines.count(expect1) == 1
+    assert result.stdout.lines.count(expect2) == 1
+
+
+def test_xpass_output(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.mark.xfail
+        def test_pass():
+            print('hi there')
+        """
+    )
+    result = pytester.runpytest("-rX")
+    result.stdout.fnmatch_lines(
+        [
+            "*= XPASSES =*",
+            "*_ test_pass _*",
+            "*- Captured stdout call -*",
+            "*= short test summary info =*",
+            "XPASS test_xpass_output.py::test_pass*",
+            "*= 1 xpassed in * =*",
+        ]
+    )
+
+
+class TestNodeIDHandling:
+    def test_nodeid_handling_windows_paths(self, pytester: Pytester, tmp_path) -> None:
+        """Test the correct handling of Windows-style paths with backslashes."""
+        pytester.makeini("[pytest]")  # Change `config.rootpath`
+
+        test_path = pytester.path / "tests" / "test_foo.py"
+        test_path.parent.mkdir()
+        os.chdir(test_path.parent)  # Change `config.invocation_params.dir`
+
+        test_path.write_text(
+            textwrap.dedent(
+                """
+                import pytest
+
+                @pytest.mark.parametrize("a", ["x/y", "C:/path", "\\\\", "C:\\\\path", "a::b/"])
+                def test_x(a):
+                    assert False
+                """
+            ),
+            encoding="utf-8",
+        )
+
+        result = pytester.runpytest("-v")
+
+        result.stdout.re_match_lines(
+            [
+                r".*test_foo.py::test_x\[x/y\] .*FAILED.*",
+                r".*test_foo.py::test_x\[C:/path\] .*FAILED.*",
+                r".*test_foo.py::test_x\[\\\\\] .*FAILED.*",
+                r".*test_foo.py::test_x\[C:\\\\path\] .*FAILED.*",
+                r".*test_foo.py::test_x\[a::b/\] .*FAILED.*",
+            ]
+        )
diff --git a/testing/test_threadexception.py b/testing/test_threadexception.py
index 5b7519f27d8..5dad07b8b85 100644
--- a/testing/test_threadexception.py
+++ b/testing/test_threadexception.py
@@ -1,11 +1,7 @@
-import sys
+from __future__ import annotations
 
-import pytest
 from _pytest.pytester import Pytester
-
-
-if sys.version_info < (3, 8):
-    pytest.skip("threadexception plugin needs Python>=3.8", allow_module_level=True)
+import pytest
 
 
 @pytest.mark.filterwarnings("default::pytest.PytestUnhandledThreadExceptionWarning")
@@ -27,7 +23,7 @@ def test_2(): pass
     )
     result = pytester.runpytest()
     assert result.ret == 0
-    assert result.parseoutcomes() == {"passed": 2, "warnings": 1}
+    result.assert_outcomes(passed=2, warnings=1)
     result.stdout.fnmatch_lines(
         [
             "*= warnings summary =*",
@@ -63,7 +59,7 @@ def test_2(): pass
     )
     result = pytester.runpytest()
     assert result.ret == 0
-    assert result.parseoutcomes() == {"passed": 2, "warnings": 1}
+    result.assert_outcomes(passed=2, warnings=1)
     result.stdout.fnmatch_lines(
         [
             "*= warnings summary =*",
@@ -100,7 +96,7 @@ def test_2(): pass
     )
     result = pytester.runpytest()
     assert result.ret == 0
-    assert result.parseoutcomes() == {"passed": 2, "warnings": 1}
+    result.assert_outcomes(passed=2, warnings=1)
     result.stdout.fnmatch_lines(
         [
             "*= warnings summary =*",
@@ -134,4 +130,126 @@ def test_2(): pass
     )
     result = pytester.runpytest()
     assert result.ret == pytest.ExitCode.TESTS_FAILED
-    assert result.parseoutcomes() == {"passed": 1, "failed": 1}
+    result.assert_outcomes(passed=1, failed=1)
+
+
+@pytest.mark.filterwarnings("error::pytest.PytestUnhandledThreadExceptionWarning")
+def test_threadexception_warning_multiple_errors(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_it="""
+        import threading
+
+        def test_it():
+            def oops():
+                raise ValueError("Oops")
+
+            t = threading.Thread(target=oops, name="MyThread")
+            t.start()
+            t.join()
+
+            t = threading.Thread(target=oops, name="MyThread2")
+            t.start()
+            t.join()
+
+        def test_2(): pass
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == pytest.ExitCode.TESTS_FAILED
+    result.assert_outcomes(passed=1, failed=1)
+    result.stdout.fnmatch_lines(
+        ["  | *ExceptionGroup: multiple thread exception warnings (2 sub-exceptions)"]
+    )
+
+
+def test_unraisable_collection_failure(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_it="""
+        import threading
+
+        class Thread(threading.Thread):
+            @property
+            def name(self):
+                raise RuntimeError("oops!")
+
+        def test_it():
+            def oops():
+                raise ValueError("Oops")
+
+            t = Thread(target=oops, name="MyThread")
+            t.start()
+            t.join()
+
+        def test_2(): pass
+        """
+    )
+
+    result = pytester.runpytest()
+    assert result.ret == 1
+    result.assert_outcomes(passed=1, failed=1)
+    result.stdout.fnmatch_lines(
+        ["E               RuntimeError: Failed to process thread exception"]
+    )
+
+
+def test_unhandled_thread_exception_after_teardown(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_it="""
+        import threading
+        import pytest
+
+        def thread():
+            def oops():
+                raise ValueError("Oops")
+
+            t = threading.Thread(target=oops, name="MyThread")
+            t.start()
+            t.join()
+
+        def test_it(request):
+            request.config.add_cleanup(thread)
+        """
+    )
+
+    result = pytester.runpytest()
+
+    # TODO: should be a test failure or error
+    assert result.ret == pytest.ExitCode.INTERNAL_ERROR
+
+    result.assert_outcomes(passed=1)
+    result.stderr.fnmatch_lines("ValueError: Oops")
+
+
+@pytest.mark.filterwarnings("error::pytest.PytestUnhandledThreadExceptionWarning")
+def test_possibly_none_excinfo(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_it="""
+        import threading
+        import types
+
+        def test_it():
+            threading.excepthook(
+                types.SimpleNamespace(
+                    exc_type=RuntimeError,
+                    exc_value=None,
+                    exc_traceback=None,
+                    thread=None,
+                )
+            )
+        """
+    )
+
+    result = pytester.runpytest()
+
+    # TODO: should be a test failure or error
+    assert result.ret == pytest.ExitCode.TESTS_FAILED
+
+    result.assert_outcomes(failed=1)
+    result.stdout.fnmatch_lines(
+        [
+            "E                   pytest.PytestUnhandledThreadExceptionWarning:"
+            " Exception in thread <unknown>",
+            "E                   ",
+            "E                   NoneType: None",
+        ]
+    )
diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py
index 4f7c5384700..016588a143d 100644
--- a/testing/test_tmpdir.py
+++ b/testing/test_tmpdir.py
@@ -1,15 +1,15 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
+from collections.abc import Callable
+import dataclasses
 import os
+from pathlib import Path
 import stat
 import sys
-import warnings
-from pathlib import Path
-from typing import Callable
 from typing import cast
-from typing import List
-
-import attr
+import warnings
 
-import pytest
 from _pytest import pathlib
 from _pytest.config import Config
 from _pytest.monkeypatch import MonkeyPatch
@@ -23,6 +23,7 @@
 from _pytest.pytester import Pytester
 from _pytest.tmpdir import get_user
 from _pytest.tmpdir import TempPathFactory
+import pytest
 
 
 def test_tmp_path_fixture(pytester: Pytester) -> None:
@@ -31,9 +32,9 @@ def test_tmp_path_fixture(pytester: Pytester) -> None:
     results.stdout.fnmatch_lines(["*1 passed*"])
 
 
-@attr.s
+@dataclasses.dataclass
 class FakeConfig:
-    basetemp = attr.ib()
+    basetemp: str | Path
 
     @property
     def trace(self):
@@ -42,13 +43,21 @@ def trace(self):
     def get(self, key):
         return lambda *k: None
 
+    def getini(self, name):
+        if name == "tmp_path_retention_count":
+            return 3
+        elif name == "tmp_path_retention_policy":
+            return "all"
+        else:
+            assert False
+
     @property
     def option(self):
         return self
 
 
 class TestTmpPathHandler:
-    def test_mktemp(self, tmp_path):
+    def test_mktemp(self, tmp_path: Path) -> None:
         config = cast(Config, FakeConfig(tmp_path))
         t = TempPathFactory.from_config(config, _ispytest=True)
         tmp = t.mktemp("world")
@@ -59,7 +68,9 @@ def test_mktemp(self, tmp_path):
         assert str(tmp2.relative_to(t.getbasetemp())).startswith("this")
         assert tmp2 != tmp
 
-    def test_tmppath_relative_basetemp_absolute(self, tmp_path, monkeypatch):
+    def test_tmppath_relative_basetemp_absolute(
+        self, tmp_path: Path, monkeypatch: MonkeyPatch
+    ) -> None:
         """#4425"""
         monkeypatch.chdir(tmp_path)
         config = cast(Config, FakeConfig("hello"))
@@ -76,14 +87,144 @@ def test_1(tmp_path):
                 pass
         """
         )
-        pytester.runpytest(p, "--basetemp=%s" % mytemp)
+        pytester.runpytest(p, f"--basetemp={mytemp}")
         assert mytemp.exists()
         mytemp.joinpath("hello").touch()
 
-        pytester.runpytest(p, "--basetemp=%s" % mytemp)
+        pytester.runpytest(p, f"--basetemp={mytemp}")
         assert mytemp.exists()
         assert not mytemp.joinpath("hello").exists()
 
+    def test_policy_failed_removes_only_passed_dir(self, pytester: Pytester) -> None:
+        p = pytester.makepyfile(
+            """
+            def test_1(tmp_path):
+                assert 0 == 0
+            def test_2(tmp_path):
+                assert 0 == 1
+        """
+        )
+        pytester.makepyprojecttoml(
+            """
+            [tool.pytest.ini_options]
+            tmp_path_retention_policy = "failed"
+        """
+        )
+
+        pytester.inline_run(p)
+        root = pytester._test_tmproot
+
+        for child in root.iterdir():
+            base_dir = list(
+                filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir())
+            )
+            assert len(base_dir) == 1
+            test_dir = list(
+                filter(
+                    lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir()
+                )
+            )
+            # Check only the failed one remains
+            assert len(test_dir) == 1
+            assert test_dir[0].name == "test_20"
+
+    def test_policy_failed_removes_basedir_when_all_passed(
+        self, pytester: Pytester
+    ) -> None:
+        p = pytester.makepyfile(
+            """
+            def test_1(tmp_path):
+                assert 0 == 0
+        """
+        )
+        pytester.makepyprojecttoml(
+            """
+            [tool.pytest.ini_options]
+            tmp_path_retention_policy = "failed"
+        """
+        )
+
+        pytester.inline_run(p)
+        root = pytester._test_tmproot
+        for child in root.iterdir():
+            # This symlink will be deleted by cleanup_numbered_dir **after**
+            # the test finishes because it's triggered by atexit.
+            # So it has to be ignored here.
+            base_dir = filter(lambda x: not x.is_symlink(), child.iterdir())
+            # Check the base dir itself is gone
+            assert len(list(base_dir)) == 0
+
+    # issue #10502
+    def test_policy_failed_removes_dir_when_skipped_from_fixture(
+        self, pytester: Pytester
+    ) -> None:
+        p = pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.fixture
+            def fixt(tmp_path):
+                pytest.skip()
+
+            def test_fixt(fixt):
+                pass
+        """
+        )
+        pytester.makepyprojecttoml(
+            """
+            [tool.pytest.ini_options]
+            tmp_path_retention_policy = "failed"
+        """
+        )
+
+        pytester.inline_run(p)
+
+        # Check if the whole directory is removed
+        root = pytester._test_tmproot
+        for child in root.iterdir():
+            base_dir = list(
+                filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir())
+            )
+            assert len(base_dir) == 0
+
+    # issue #10502
+    def test_policy_all_keeps_dir_when_skipped_from_fixture(
+        self, pytester: Pytester
+    ) -> None:
+        p = pytester.makepyfile(
+            """
+            import pytest
+
+            @pytest.fixture
+            def fixt(tmp_path):
+                pytest.skip()
+
+            def test_fixt(fixt):
+                pass
+        """
+        )
+        pytester.makepyprojecttoml(
+            """
+            [tool.pytest.ini_options]
+            tmp_path_retention_policy = "all"
+        """
+        )
+        pytester.inline_run(p)
+
+        # Check if the whole directory is kept
+        root = pytester._test_tmproot
+        for child in root.iterdir():
+            base_dir = list(
+                filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir())
+            )
+            assert len(base_dir) == 1
+            test_dir = list(
+                filter(
+                    lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir()
+                )
+            )
+            assert len(test_dir) == 1
+
 
 testdata = [
     ("mypath", True),
@@ -101,15 +242,13 @@ def test_1(tmp_path):
 def test_mktemp(pytester: Pytester, basename: str, is_ok: bool) -> None:
     mytemp = pytester.mkdir("mytemp")
     p = pytester.makepyfile(
-        """
+        f"""
         def test_abs_path(tmp_path_factory):
-            tmp_path_factory.mktemp('{}', numbered=False)
-        """.format(
-            basename
-        )
+            tmp_path_factory.mktemp('{basename}', numbered=False)
+        """
     )
 
-    result = pytester.runpytest(p, "--basetemp=%s" % mytemp)
+    result = pytester.runpytest(p, f"--basetemp={mytemp}")
     if is_ok:
         assert result.ret == 0
         assert mytemp.joinpath(basename).exists()
@@ -197,7 +336,6 @@ def test_tmp_path_fallback_uid_not_found(pytester: Pytester) -> None:
     """Test that tmp_path works even if the current process's user id does not
     correspond to a valid user.
     """
-
     pytester.makepyfile(
         """
         def test_some(tmp_path):
@@ -256,7 +394,7 @@ def test_cleanup_lock_create(self, tmp_path):
     def test_lock_register_cleanup_removal(self, tmp_path: Path) -> None:
         lock = create_cleanup_lock(tmp_path)
 
-        registry: List[Callable[..., None]] = []
+        registry: list[Callable[..., None]] = []
         register_cleanup_lock_removal(lock, register=registry.append)
 
         (cleanup_func,) = registry
@@ -275,12 +413,12 @@ def test_lock_register_cleanup_removal(self, tmp_path: Path) -> None:
 
         assert not lock.exists()
 
-    def _do_cleanup(self, tmp_path: Path) -> None:
+    def _do_cleanup(self, tmp_path: Path, keep: int = 2) -> None:
         self.test_make(tmp_path)
         cleanup_numbered_dir(
             root=tmp_path,
             prefix=self.PREFIX,
-            keep=2,
+            keep=keep,
             consider_lock_dead_if_created_before=0,
         )
 
@@ -289,6 +427,11 @@ def test_cleanup_keep(self, tmp_path):
         a, b = (x for x in tmp_path.iterdir() if not x.is_symlink())
         print(a, b)
 
+    def test_cleanup_keep_0(self, tmp_path: Path):
+        self._do_cleanup(tmp_path, 0)
+        dir_num = len(list(tmp_path.iterdir()))
+        assert dir_num == 0
+
     def test_cleanup_locked(self, tmp_path):
         p = make_numbered_dir(root=tmp_path, prefix=self.PREFIX)
 
@@ -367,33 +510,31 @@ def test_on_rm_rf_error(self, tmp_path: Path) -> None:
 
         # unknown exception
         with pytest.warns(pytest.PytestWarning):
-            exc_info1 = (None, RuntimeError(), None)
+            exc_info1 = (RuntimeError, RuntimeError(), None)
             on_rm_rf_error(os.unlink, str(fn), exc_info1, start_path=tmp_path)
             assert fn.is_file()
 
         # we ignore FileNotFoundError
-        exc_info2 = (None, FileNotFoundError(), None)
+        exc_info2 = (FileNotFoundError, FileNotFoundError(), None)
         assert not on_rm_rf_error(None, str(fn), exc_info2, start_path=tmp_path)
 
         # unknown function
         with pytest.warns(
             pytest.PytestWarning,
-            match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\nNone: ",
+            match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\n<class 'PermissionError'>: ",
         ):
-            exc_info3 = (None, PermissionError(), None)
+            exc_info3 = (PermissionError, PermissionError(), None)
             on_rm_rf_error(None, str(fn), exc_info3, start_path=tmp_path)
             assert fn.is_file()
 
         # ignored function
-        with warnings.catch_warnings():
-            warnings.simplefilter("ignore")
-            with pytest.warns(None) as warninfo:  # type: ignore[call-overload]
-                exc_info4 = (None, PermissionError(), None)
-                on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path)
-                assert fn.is_file()
-            assert not [x.message for x in warninfo]
-
-        exc_info5 = (None, PermissionError(), None)
+        with warnings.catch_warnings(record=True) as w:
+            exc_info4 = PermissionError()
+            on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path)
+            assert fn.is_file()
+            assert not [x.message for x in w]
+
+        exc_info5 = PermissionError()
         on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path)
         assert not fn.is_file()
 
@@ -416,7 +557,7 @@ def test_basetemp_with_read_only_files(pytester: Pytester) -> None:
 
         def test(tmp_path):
             fn = tmp_path / 'foo.txt'
-            fn.write_text('hello')
+            fn.write_text('hello', encoding='utf-8')
             mode = os.stat(str(fn)).st_mode
             os.chmod(str(fn), mode & ~stat.S_IREAD)
     """
@@ -446,7 +587,7 @@ def test_tmp_path_factory_create_directory_with_safe_permissions(
     """Verify that pytest creates directories under /tmp with private permissions."""
     # Use the test's tmp_path as the system temproot (/tmp).
     monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path))
-    tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True)
+    tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True)
     basetemp = tmp_factory.getbasetemp()
 
     # No world-readable permissions.
@@ -466,14 +607,14 @@ def test_tmp_path_factory_fixes_up_world_readable_permissions(
     """
     # Use the test's tmp_path as the system temproot (/tmp).
     monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path))
-    tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True)
+    tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True)
     basetemp = tmp_factory.getbasetemp()
 
     # Before - simulate bad perms.
     os.chmod(basetemp.parent, 0o777)
     assert (basetemp.parent.stat().st_mode & 0o077) != 0
 
-    tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True)
+    tmp_factory = TempPathFactory(None, 3, "all", lambda *args: None, _ispytest=True)
     basetemp = tmp_factory.getbasetemp()
 
     # After - fixed.
diff --git a/testing/test_unittest.py b/testing/test_unittest.py
index 12bcb9361a4..56224c08228 100644
--- a/testing/test_unittest.py
+++ b/testing/test_unittest.py
@@ -1,11 +1,12 @@
-import gc
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import sys
-from typing import List
 
-import pytest
 from _pytest.config import ExitCode
 from _pytest.monkeypatch import MonkeyPatch
 from _pytest.pytester import Pytester
+import pytest
 
 
 def test_simple_unittest(pytester: Pytester) -> None:
@@ -191,26 +192,35 @@ def test_check(self):
 def test_teardown_issue1649(pytester: Pytester) -> None:
     """
     Are TestCase objects cleaned up? Often unittest TestCase objects set
-    attributes that are large and expensive during setUp.
+    attributes that are large and expensive during test run or setUp.
 
     The TestCase will not be cleaned up if the test fails, because it
     would then exist in the stackframe.
+
+    Regression test for #1649 (see also #12367).
     """
-    testpath = pytester.makepyfile(
+    pytester.makepyfile(
         """
         import unittest
-        class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase):
-            def setUp(self):
-                self.an_expensive_object = 1
-            def test_demo(self):
-                pass
+        import gc
 
-    """
+        class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase):
+            def test_expensive(self):
+                self.an_expensive_obj = object()
+
+            def test_is_it_still_alive(self):
+                gc.collect()
+                for obj in gc.get_objects():
+                    if type(obj).__name__ == "TestCaseObjectsShouldBeCleanedUp":
+                        assert not hasattr(obj, "an_expensive_obj")
+                        break
+                else:
+                    assert False, "Could not find TestCaseObjectsShouldBeCleanedUp instance"
+        """
     )
-    pytester.inline_run("-s", testpath)
-    gc.collect()
-    for obj in gc.get_objects():
-        assert type(obj).__name__ != "TestCaseObjectsShouldBeCleanedUp"
+
+    result = pytester.runpytest()
+    assert result.ret == ExitCode.OK
 
 
 def test_unittest_skip_issue148(pytester: Pytester) -> None:
@@ -294,7 +304,7 @@ def test_func2(self):
             @classmethod
             def tearDownClass(cls):
                 cls.x -= 1
-        def test_teareddown():
+        def test_torn_down():
             assert MyTestCase.x == 0
     """
     )
@@ -341,7 +351,7 @@ def test_func2(self):
                 assert self.x == 1
             def teardown_class(cls):
                 cls.x -= 1
-        def test_teareddown():
+        def test_torn_down():
             assert MyTestCase.x == 0
     """
     )
@@ -352,22 +362,21 @@ def test_teareddown():
 @pytest.mark.parametrize("type", ["Error", "Failure"])
 def test_testcase_adderrorandfailure_defers(pytester: Pytester, type: str) -> None:
     pytester.makepyfile(
-        """
+        f"""
         from unittest import TestCase
         import pytest
         class MyTestCase(TestCase):
             def run(self, result):
                 excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0)
                 try:
-                    result.add%s(self, excinfo._excinfo)
+                    result.add{type}(self, excinfo._excinfo)
                 except KeyboardInterrupt:
                     raise
                 except:
-                    pytest.fail("add%s should not raise")
+                    pytest.fail("add{type} should not raise")
             def test_hello(self):
                 pass
     """
-        % (type, type)
     )
     result = pytester.runpytest()
     result.stdout.no_fnmatch_line("*should not raise*")
@@ -376,7 +385,7 @@ def test_hello(self):
 @pytest.mark.parametrize("type", ["Error", "Failure"])
 def test_testcase_custom_exception_info(pytester: Pytester, type: str) -> None:
     pytester.makepyfile(
-        """
+        f"""
         from typing import Generic, TypeVar
         from unittest import TestCase
         import pytest, _pytest._code
@@ -399,14 +408,13 @@ def from_exc_info(cls, *args, **kwargs):
                 mp.setattr(_pytest._code, 'ExceptionInfo', FakeExceptionInfo)
                 try:
                     excinfo = excinfo._excinfo
-                    result.add%(type)s(self, excinfo)
+                    result.add{type}(self, excinfo)
                 finally:
                     mp.undo()
 
             def test_hello(self):
                 pass
     """
-        % locals()
     )
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
@@ -833,7 +841,7 @@ def test_passing_test_is_fail(self):
 @pytest.mark.parametrize("stmt", ["return", "yield"])
 def test_unittest_setup_interaction(pytester: Pytester, stmt: str) -> None:
     pytester.makepyfile(
-        """
+        f"""
         import unittest
         import pytest
         class MyTestCase(unittest.TestCase):
@@ -855,9 +863,7 @@ def test_method2(self):
 
             def test_classattr(self):
                 assert self.__class__.hello == "world"
-    """.format(
-            stmt=stmt
-        )
+    """
     )
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(["*3 passed*"])
@@ -880,7 +886,7 @@ def test_method1(self):
             def tearDownClass(cls):
                 cls.x = 1
 
-        def test_not_teareddown():
+        def test_not_torn_down():
             assert TestFoo.x == 0
 
     """
@@ -952,7 +958,7 @@ def test_issue333_result_clearing(pytester: Pytester) -> None:
     pytester.makeconftest(
         """
         import pytest
-        @pytest.hookimpl(hookwrapper=True)
+        @pytest.hookimpl(wrapper=True)
         def pytest_runtest_call(item):
             yield
             assert 0
@@ -1062,7 +1068,7 @@ def pytest_collection_modifyitems(items):
     )
 
     pytester.makepyfile(
-        """
+        f"""
         import pytest
         import {module}
 
@@ -1081,9 +1087,7 @@ def test_two(self):
                 assert self.fixture2
 
 
-    """.format(
-            module=module, base=base
-        )
+    """
     )
 
     result = pytester.runpytest("-s")
@@ -1211,7 +1215,7 @@ def test_pdb_teardown_called(pytester: Pytester, monkeypatch: MonkeyPatch) -> No
     We delay the normal tearDown() calls when --pdb is given, so this ensures we are calling
     tearDown() eventually to avoid memory leaks when using --pdb.
     """
-    teardowns: List[str] = []
+    teardowns: list[str] = []
     monkeypatch.setattr(
         pytest, "test_pdb_teardown_called_teardowns", teardowns, raising=False
     )
@@ -1241,33 +1245,69 @@ def test_2(self):
 
 
 @pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"])
-def test_pdb_teardown_skipped(
+def test_pdb_teardown_skipped_for_functions(
     pytester: Pytester, monkeypatch: MonkeyPatch, mark: str
 ) -> None:
-    """With --pdb, setUp and tearDown should not be called for skipped tests."""
-    tracked: List[str] = []
-    monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False)
+    """
+    With --pdb, setUp and tearDown should not be called for tests skipped
+    via a decorator (#7215).
+    """
+    tracked: list[str] = []
+    monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False)
 
     pytester.makepyfile(
-        """
+        f"""
         import unittest
         import pytest
 
         class MyTestCase(unittest.TestCase):
 
             def setUp(self):
-                pytest.test_pdb_teardown_skipped.append("setUp:" + self.id())
+                pytest.track_pdb_teardown_skipped.append("setUp:" + self.id())
 
             def tearDown(self):
-                pytest.test_pdb_teardown_skipped.append("tearDown:" + self.id())
+                pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id())
 
             {mark}("skipped for reasons")
             def test_1(self):
                 pass
 
-    """.format(
-            mark=mark
-        )
+    """
+    )
+    result = pytester.runpytest_inprocess("--pdb")
+    result.stdout.fnmatch_lines("* 1 skipped in *")
+    assert tracked == []
+
+
+@pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"])
+def test_pdb_teardown_skipped_for_classes(
+    pytester: Pytester, monkeypatch: MonkeyPatch, mark: str
+) -> None:
+    """
+    With --pdb, setUp and tearDown should not be called for tests skipped
+    via a decorator on the class (#10060).
+    """
+    tracked: list[str] = []
+    monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False)
+
+    pytester.makepyfile(
+        f"""
+        import unittest
+        import pytest
+
+        {mark}("skipped for reasons")
+        class MyTestCase(unittest.TestCase):
+
+            def setUp(self):
+                pytest.track_pdb_teardown_skipped.append("setUp:" + self.id())
+
+            def tearDown(self):
+                pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id())
+
+            def test_1(self):
+                pass
+
+    """
     )
     result = pytester.runpytest_inprocess("--pdb")
     result.stdout.fnmatch_lines("* 1 skipped in *")
@@ -1314,9 +1354,6 @@ def test_plain_unittest_does_not_support_async(pytester: Pytester) -> None:
     result.stdout.fnmatch_lines(expected_lines)
 
 
-@pytest.mark.skipif(
-    sys.version_info < (3, 8), reason="Feature introduced in Python 3.8"
-)
 def test_do_class_cleanups_on_success(pytester: Pytester) -> None:
     testpath = pytester.makepyfile(
         """
@@ -1342,9 +1379,6 @@ def test_cleanup_called_exactly_once():
     assert passed == 3
 
 
-@pytest.mark.skipif(
-    sys.version_info < (3, 8), reason="Feature introduced in Python 3.8"
-)
 def test_do_class_cleanups_on_setupclass_failure(pytester: Pytester) -> None:
     testpath = pytester.makepyfile(
         """
@@ -1369,9 +1403,6 @@ def test_cleanup_called_exactly_once():
     assert passed == 1
 
 
-@pytest.mark.skipif(
-    sys.version_info < (3, 8), reason="Feature introduced in Python 3.8"
-)
 def test_do_class_cleanups_on_teardownclass_failure(pytester: Pytester) -> None:
     testpath = pytester.makepyfile(
         """
@@ -1472,3 +1503,173 @@ def test_cleanup_called_the_right_number_of_times():
     passed, skipped, failed = reprec.countoutcomes()
     assert failed == 2
     assert passed == 1
+
+
+class TestClassCleanupErrors:
+    """
+    Make sure to show exceptions raised during class cleanup function (those registered
+    via addClassCleanup()).
+
+    See #11728.
+    """
+
+    def test_class_cleanups_failure_in_setup(self, pytester: Pytester) -> None:
+        testpath = pytester.makepyfile(
+            """
+            import unittest
+            class MyTestCase(unittest.TestCase):
+                @classmethod
+                def setUpClass(cls):
+                    def cleanup(n):
+                        raise Exception(f"fail {n}")
+                    cls.addClassCleanup(cleanup, 2)
+                    cls.addClassCleanup(cleanup, 1)
+                    raise Exception("fail 0")
+                def test(self):
+                    pass
+        """
+        )
+        result = pytester.runpytest("-s", testpath)
+        result.assert_outcomes(passed=0, errors=1)
+        result.stdout.fnmatch_lines(
+            [
+                "*Unittest class cleanup errors *2 sub-exceptions*",
+                "*Exception: fail 1",
+                "*Exception: fail 2",
+            ]
+        )
+        result.stdout.fnmatch_lines(
+            [
+                "* ERROR at setup of MyTestCase.test *",
+                "E * Exception: fail 0",
+            ]
+        )
+
+    def test_class_cleanups_failure_in_teardown(self, pytester: Pytester) -> None:
+        testpath = pytester.makepyfile(
+            """
+            import unittest
+            class MyTestCase(unittest.TestCase):
+                @classmethod
+                def setUpClass(cls):
+                    def cleanup(n):
+                        raise Exception(f"fail {n}")
+                    cls.addClassCleanup(cleanup, 2)
+                    cls.addClassCleanup(cleanup, 1)
+                def test(self):
+                    pass
+        """
+        )
+        result = pytester.runpytest("-s", testpath)
+        result.assert_outcomes(passed=1, errors=1)
+        result.stdout.fnmatch_lines(
+            [
+                "*Unittest class cleanup errors *2 sub-exceptions*",
+                "*Exception: fail 1",
+                "*Exception: fail 2",
+            ]
+        )
+
+    def test_class_cleanup_1_failure_in_teardown(self, pytester: Pytester) -> None:
+        testpath = pytester.makepyfile(
+            """
+            import unittest
+            class MyTestCase(unittest.TestCase):
+                @classmethod
+                def setUpClass(cls):
+                    def cleanup(n):
+                        raise Exception(f"fail {n}")
+                    cls.addClassCleanup(cleanup, 1)
+                def test(self):
+                    pass
+        """
+        )
+        result = pytester.runpytest("-s", testpath)
+        result.assert_outcomes(passed=1, errors=1)
+        result.stdout.fnmatch_lines(
+            [
+                "*ERROR at teardown of MyTestCase.test*",
+                "*Exception: fail 1",
+            ]
+        )
+
+
+def test_traceback_pruning(pytester: Pytester) -> None:
+    """Regression test for #9610 - doesn't crash during traceback pruning."""
+    pytester.makepyfile(
+        """
+        import unittest
+
+        class MyTestCase(unittest.TestCase):
+            def __init__(self, test_method):
+                unittest.TestCase.__init__(self, test_method)
+
+        class TestIt(MyTestCase):
+            @classmethod
+            def tearDownClass(cls) -> None:
+                assert False
+
+            def test_it(self):
+                pass
+        """
+    )
+    reprec = pytester.inline_run()
+    passed, skipped, failed = reprec.countoutcomes()
+    assert passed == 1
+    assert failed == 1
+    assert reprec.ret == 1
+
+
+def test_raising_unittest_skiptest_during_collection(
+    pytester: Pytester,
+) -> None:
+    pytester.makepyfile(
+        """
+        import unittest
+
+        class TestIt(unittest.TestCase):
+            def test_it(self): pass
+            def test_it2(self): pass
+
+        raise unittest.SkipTest()
+
+        class TestIt2(unittest.TestCase):
+            def test_it(self): pass
+            def test_it2(self): pass
+        """
+    )
+    reprec = pytester.inline_run()
+    passed, skipped, failed = reprec.countoutcomes()
+    assert passed == 0
+    # Unittest reports one fake test for a skipped module.
+    assert skipped == 1
+    assert failed == 0
+    assert reprec.ret == ExitCode.NO_TESTS_COLLECTED
+
+
+def test_abstract_testcase_is_not_collected(pytester: Pytester) -> None:
+    """Regression test for #12275."""
+    pytester.makepyfile(
+        """
+        import abc
+        import unittest
+
+        class TestBase(unittest.TestCase, abc.ABC):
+            @abc.abstractmethod
+            def abstract1(self): pass
+
+            @abc.abstractmethod
+            def abstract2(self): pass
+
+            def test_it(self): pass
+
+        class TestPartial(TestBase):
+            def abstract1(self): pass
+
+        class TestConcrete(TestPartial):
+            def abstract2(self): pass
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == ExitCode.OK
+    result.assert_outcomes(passed=1)
diff --git a/testing/test_unraisableexception.py b/testing/test_unraisableexception.py
index f625833dcea..328177a7ba3 100644
--- a/testing/test_unraisableexception.py
+++ b/testing/test_unraisableexception.py
@@ -1,13 +1,19 @@
+from __future__ import annotations
+
+from collections.abc import Generator
+import contextlib
+import gc
 import sys
+from unittest import mock
 
-import pytest
 from _pytest.pytester import Pytester
+import pytest
 
 
-if sys.version_info < (3, 8):
-    pytest.skip("unraisableexception plugin needs Python>=3.8", allow_module_level=True)
+PYPY = hasattr(sys, "pypy_version_info")
 
 
+@pytest.mark.skipif(PYPY, reason="garbage-collection differences make this flaky")
 @pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning")
 def test_unraisable(pytester: Pytester) -> None:
     pytester.makepyfile(
@@ -25,7 +31,7 @@ def test_2(): pass
     )
     result = pytester.runpytest()
     assert result.ret == 0
-    assert result.parseoutcomes() == {"passed": 2, "warnings": 1}
+    result.assert_outcomes(passed=2, warnings=1)
     result.stdout.fnmatch_lines(
         [
             "*= warnings summary =*",
@@ -35,11 +41,14 @@ def test_2(): pass
             "  Traceback (most recent call last):",
             "  ValueError: del is broken",
             "  ",
+            "  Enable tracemalloc to get traceback where the object was allocated.",
+            "  See https* for more info.",
             "    warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))",
         ]
     )
 
 
+@pytest.mark.skipif(PYPY, reason="garbage-collection differences make this flaky")
 @pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning")
 def test_unraisable_in_setup(pytester: Pytester) -> None:
     pytester.makepyfile(
@@ -61,7 +70,7 @@ def test_2(): pass
     )
     result = pytester.runpytest()
     assert result.ret == 0
-    assert result.parseoutcomes() == {"passed": 2, "warnings": 1}
+    result.assert_outcomes(passed=2, warnings=1)
     result.stdout.fnmatch_lines(
         [
             "*= warnings summary =*",
@@ -71,11 +80,14 @@ def test_2(): pass
             "  Traceback (most recent call last):",
             "  ValueError: del is broken",
             "  ",
+            "  Enable tracemalloc to get traceback where the object was allocated.",
+            "  See https* for more info.",
             "    warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))",
         ]
     )
 
 
+@pytest.mark.skipif(PYPY, reason="garbage-collection differences make this flaky")
 @pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning")
 def test_unraisable_in_teardown(pytester: Pytester) -> None:
     pytester.makepyfile(
@@ -98,7 +110,7 @@ def test_2(): pass
     )
     result = pytester.runpytest()
     assert result.ret == 0
-    assert result.parseoutcomes() == {"passed": 2, "warnings": 1}
+    result.assert_outcomes(passed=2, warnings=1)
     result.stdout.fnmatch_lines(
         [
             "*= warnings summary =*",
@@ -108,6 +120,8 @@ def test_2(): pass
             "  Traceback (most recent call last):",
             "  ValueError: del is broken",
             "  ",
+            "  Enable tracemalloc to get traceback where the object was allocated.",
+            "  See https* for more info.",
             "    warnings.warn(pytest.PytestUnraisableExceptionWarning(msg))",
         ]
     )
@@ -116,7 +130,7 @@ def test_2(): pass
 @pytest.mark.filterwarnings("error::pytest.PytestUnraisableExceptionWarning")
 def test_unraisable_warning_error(pytester: Pytester) -> None:
     pytester.makepyfile(
-        test_it="""
+        test_it=f"""
         class BrokenDel:
             def __del__(self) -> None:
                 raise ValueError("del is broken")
@@ -124,10 +138,248 @@ def __del__(self) -> None:
         def test_it() -> None:
             obj = BrokenDel()
             del obj
+            {"import gc; gc.collect()" * PYPY}
 
         def test_2(): pass
         """
     )
     result = pytester.runpytest()
     assert result.ret == pytest.ExitCode.TESTS_FAILED
-    assert result.parseoutcomes() == {"passed": 1, "failed": 1}
+    result.assert_outcomes(passed=1, failed=1)
+
+
+@pytest.mark.filterwarnings("error::pytest.PytestUnraisableExceptionWarning")
+def test_unraisable_warning_multiple_errors(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_it=f"""
+        class BrokenDel:
+            def __init__(self, msg: str):
+                self.msg = msg
+
+            def __del__(self) -> None:
+                raise ValueError(self.msg)
+
+        def test_it() -> None:
+            BrokenDel("del is broken 1")
+            BrokenDel("del is broken 2")
+            {"import gc; gc.collect()" * PYPY}
+
+        def test_2(): pass
+        """
+    )
+    result = pytester.runpytest()
+    assert result.ret == pytest.ExitCode.TESTS_FAILED
+    result.assert_outcomes(passed=1, failed=1)
+    result.stdout.fnmatch_lines(
+        [
+            "  | *ExceptionGroup: multiple unraisable exception warnings (2 sub-exceptions)"
+        ]
+    )
+
+
+def test_unraisable_collection_failure(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_it=f"""
+        class BrokenDel:
+            def __del__(self):
+                raise ValueError("del is broken")
+
+        def test_it():
+            obj = BrokenDel()
+            del obj
+            {"import gc; gc.collect()" * PYPY}
+
+        def test_2(): pass
+        """
+    )
+
+    class MyError(BaseException):
+        pass
+
+    with mock.patch("traceback.format_exception", side_effect=MyError):
+        result = pytester.runpytest()
+    assert result.ret == 1
+    result.assert_outcomes(passed=1, failed=1)
+    result.stdout.fnmatch_lines(
+        ["E               RuntimeError: Failed to process unraisable exception"]
+    )
+
+
+def _set_gc_state(enabled: bool) -> bool:
+    was_enabled = gc.isenabled()
+    if enabled:
+        gc.enable()
+    else:
+        gc.disable()
+    return was_enabled
+
+
+@contextlib.contextmanager
+def _disable_gc() -> Generator[None]:
+    was_enabled = _set_gc_state(enabled=False)
+    try:
+        yield
+    finally:
+        _set_gc_state(enabled=was_enabled)
+
+
+def test_refcycle_unraisable(pytester: Pytester) -> None:
+    # see: https://github.com/pytest-dev/pytest/issues/10404
+    pytester.makepyfile(
+        test_it="""
+        import pytest
+
+        class BrokenDel:
+            def __init__(self):
+                self.self = self  # make a reference cycle
+
+            def __del__(self):
+                raise ValueError("del is broken")
+
+        def test_it():
+            BrokenDel()
+        """
+    )
+
+    with _disable_gc():
+        result = pytester.runpytest()
+
+    # TODO: should be a test failure or error
+    assert result.ret == pytest.ExitCode.INTERNAL_ERROR
+
+    result.assert_outcomes(passed=1)
+    result.stderr.fnmatch_lines("ValueError: del is broken")
+
+
+@pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning")
+def test_refcycle_unraisable_warning_filter(pytester: Pytester) -> None:
+    # note that the host pytest warning filter is disabled and the pytester
+    # warning filter applies during config teardown of unraisablehook.
+    # see: https://github.com/pytest-dev/pytest/issues/10404
+    pytester.makepyfile(
+        test_it="""
+        import pytest
+
+        class BrokenDel:
+            def __init__(self):
+                self.self = self  # make a reference cycle
+
+            def __del__(self):
+                raise ValueError("del is broken")
+
+        def test_it():
+            BrokenDel()
+        """
+    )
+
+    with _disable_gc():
+        result = pytester.runpytest("-Werror")
+
+    # TODO: should be a test failure or error
+    assert result.ret == pytest.ExitCode.INTERNAL_ERROR
+
+    result.assert_outcomes(passed=1)
+    result.stderr.fnmatch_lines("ValueError: del is broken")
+
+
+@pytest.mark.filterwarnings("default::pytest.PytestUnraisableExceptionWarning")
+def test_create_task_raises_unraisable_warning_filter(pytester: Pytester) -> None:
+    # note that the host pytest warning filter is disabled and the pytester
+    # warning filter applies during config teardown of unraisablehook.
+    # see: https://github.com/pytest-dev/pytest/issues/10404
+    # This is a dupe of the above test, but using the exact reproducer from
+    # the issue
+    pytester.makepyfile(
+        test_it="""
+        import asyncio
+        import pytest
+
+        async def my_task():
+            pass
+
+        def test_scheduler_must_be_created_within_running_loop() -> None:
+            with pytest.raises(RuntimeError) as _:
+                asyncio.create_task(my_task())
+        """
+    )
+
+    with _disable_gc():
+        result = pytester.runpytest("-Werror")
+
+    # TODO: should be a test failure or error
+    assert result.ret == pytest.ExitCode.INTERNAL_ERROR
+
+    result.assert_outcomes(passed=1)
+    result.stderr.fnmatch_lines("RuntimeWarning: coroutine 'my_task' was never awaited")
+
+
+def test_refcycle_unraisable_warning_filter_default(pytester: Pytester) -> None:
+    # note this time we use a default warning filter for pytester
+    # and run it in a subprocess, because the warning can only go to the
+    # sys.stdout rather than the terminal reporter, which has already
+    # finished.
+    # see: https://github.com/pytest-dev/pytest/pull/13057#discussion_r1888396126
+    pytester.makepyfile(
+        test_it="""
+        import gc
+        gc.disable()
+
+        import pytest
+
+        class BrokenDel:
+            def __init__(self):
+                self.self = self  # make a reference cycle
+
+            def __del__(self):
+                raise ValueError("del is broken")
+
+        def test_it():
+            BrokenDel()
+        """
+    )
+
+    # since we use subprocess we need to disable gc inside test_it
+    result = pytester.runpytest_subprocess("-Wdefault")
+
+    assert result.ret == pytest.ExitCode.OK
+
+    # TODO: should be warnings=1, but the outcome has already come out
+    # by the time the warning triggers
+    result.assert_outcomes(passed=1)
+    result.stderr.fnmatch_lines("ValueError: del is broken")
+
+
+@pytest.mark.filterwarnings("error::pytest.PytestUnraisableExceptionWarning")
+def test_possibly_none_excinfo(pytester: Pytester) -> None:
+    pytester.makepyfile(
+        test_it="""
+        import sys
+        import types
+
+        def test_it():
+            sys.unraisablehook(
+                types.SimpleNamespace(
+                    exc_type=RuntimeError,
+                    exc_value=None,
+                    exc_traceback=None,
+                    err_msg=None,
+                    object=None,
+                )
+            )
+        """
+    )
+
+    result = pytester.runpytest()
+
+    # TODO: should be a test failure or error
+    assert result.ret == pytest.ExitCode.TESTS_FAILED
+
+    result.assert_outcomes(failed=1)
+    result.stdout.fnmatch_lines(
+        [
+            "E                   pytest.PytestUnraisableExceptionWarning:"
+            " Exception ignored in: None",
+            "E                   ",
+            "E                   NoneType: None",
+        ]
+    )
diff --git a/testing/test_warning_types.py b/testing/test_warning_types.py
index b49cc68f9c6..7cbc4703c26 100644
--- a/testing/test_warning_types.py
+++ b/testing/test_warning_types.py
@@ -1,8 +1,11 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import inspect
 
-import pytest
 from _pytest import warning_types
 from _pytest.pytester import Pytester
+import pytest
 
 
 @pytest.mark.parametrize(
@@ -36,3 +39,12 @@ def test():
     )
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(["E       pytest.PytestWarning: some warning"])
+
+
+@pytest.mark.filterwarnings("error")
+def test_warn_explicit_for_annotates_errors_with_location():
+    with pytest.raises(Warning, match="(?m)test\n at .*raises.py:\\d+"):
+        warning_types.warn_explicit_for(
+            pytest.raises,  # type: ignore[arg-type]
+            warning_types.PytestWarning("test"),
+        )
diff --git a/testing/test_warnings.py b/testing/test_warnings.py
index cac716680f4..c302e7c6e3c 100644
--- a/testing/test_warnings.py
+++ b/testing/test_warnings.py
@@ -1,12 +1,14 @@
+# mypy: allow-untyped-defs
+from __future__ import annotations
+
 import os
+import sys
 import warnings
-from typing import List
-from typing import Optional
-from typing import Tuple
 
-import pytest
 from _pytest.fixtures import FixtureRequest
 from _pytest.pytester import Pytester
+import pytest
+
 
 WARNINGS_SUMMARY_HEADER = "warnings summary"
 
@@ -15,16 +17,13 @@
 def pyfile_with_warnings(pytester: Pytester, request: FixtureRequest) -> str:
     """Create a test file which calls a function in a module which generates warnings."""
     pytester.syspathinsert()
-    test_name = request.function.__name__
-    module_name = test_name.lstrip("test_") + "_module"
+    module_name = request.function.__name__[len("test_") :] + "_module"
     test_file = pytester.makepyfile(
-        """
+        f"""
         import {module_name}
         def test_func():
             assert {module_name}.foo() == 1
-        """.format(
-            module_name=module_name
-        ),
+        """,
         **{
             module_name: """
             import warnings
@@ -44,7 +43,7 @@ def test_normal_flow(pytester: Pytester, pyfile_with_warnings) -> None:
     result = pytester.runpytest(pyfile_with_warnings)
     result.stdout.fnmatch_lines(
         [
-            "*== %s ==*" % WARNINGS_SUMMARY_HEADER,
+            f"*== {WARNINGS_SUMMARY_HEADER} ==*",
             "test_normal_flow.py::test_func",
             "*normal_flow_module.py:3: UserWarning: user warning",
             '*  warnings.warn(UserWarning("user warning"))',
@@ -75,7 +74,7 @@ def test_func(fix):
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
         [
-            "*== %s ==*" % WARNINGS_SUMMARY_HEADER,
+            f"*== {WARNINGS_SUMMARY_HEADER} ==*",
             "*test_setup_teardown_warnings.py:6: UserWarning: warning during setup",
             '*warnings.warn(UserWarning("warning during setup"))',
             "*test_setup_teardown_warnings.py:8: UserWarning: warning during teardown",
@@ -143,7 +142,7 @@ def test_func(fix):
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
         [
-            "*== %s ==*" % WARNINGS_SUMMARY_HEADER,
+            f"*== {WARNINGS_SUMMARY_HEADER} ==*",
             "*test_unicode.py:7: UserWarning: \u6d4b\u8bd5*",
             "* 1 passed, 1 warning*",
         ]
@@ -239,7 +238,7 @@ def test_func():
 
 
 @pytest.mark.filterwarnings("always::UserWarning")
-def test_warning_captured_hook(pytester: Pytester) -> None:
+def test_warning_recorded_hook(pytester: Pytester) -> None:
     pytester.makeconftest(
         """
         def pytest_configure(config):
@@ -276,20 +275,18 @@ def pytest_warning_recorded(self, warning_message, when, nodeid, location):
     expected = [
         ("config warning", "config", ""),
         ("collect warning", "collect", ""),
-        ("setup warning", "runtest", "test_warning_captured_hook.py::test_func"),
-        ("call warning", "runtest", "test_warning_captured_hook.py::test_func"),
-        ("teardown warning", "runtest", "test_warning_captured_hook.py::test_func"),
+        ("setup warning", "runtest", "test_warning_recorded_hook.py::test_func"),
+        ("call warning", "runtest", "test_warning_recorded_hook.py::test_func"),
+        ("teardown warning", "runtest", "test_warning_recorded_hook.py::test_func"),
     ]
-    for index in range(len(expected)):
-        collected_result = collected[index]
-        expected_result = expected[index]
-
+    assert len(collected) == len(expected)  # python < 3.10 zip(strict=True)
+    for collected_result, expected_result in zip(collected, expected):
         assert collected_result[0] == expected_result[0], str(collected)
         assert collected_result[1] == expected_result[1], str(collected)
         assert collected_result[2] == expected_result[2], str(collected)
 
         # NOTE: collected_result[3] is location, which differs based on the platform you are on
-        #       thus, the best we can do here is assert the types of the paremeters match what we expect
+        #       thus, the best we can do here is assert the types of the parameters match what we expect
         #       and not try and preload it in the expected array
         if collected_result[3] is not None:
             assert type(collected_result[3][0]) is str, str(collected)
@@ -315,7 +312,7 @@ def test_foo():
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
         [
-            "*== %s ==*" % WARNINGS_SUMMARY_HEADER,
+            f"*== {WARNINGS_SUMMARY_HEADER} ==*",
             "  *collection_warnings.py:3: UserWarning: collection warning",
             '    warnings.warn(UserWarning("collection warning"))',
             "* 1 passed, 1 warning*",
@@ -374,7 +371,7 @@ def test_bar():
     else:
         result.stdout.fnmatch_lines(
             [
-                "*== %s ==*" % WARNINGS_SUMMARY_HEADER,
+                f"*== {WARNINGS_SUMMARY_HEADER} ==*",
                 "*test_hide_pytest_internal_warnings.py:4: PytestWarning: some internal warning",
                 "* 1 passed, 1 warning *",
             ]
@@ -435,7 +432,7 @@ class TestDeprecationWarningsByDefault:
 
     def create_file(self, pytester: Pytester, mark="") -> None:
         pytester.makepyfile(
-            """
+            f"""
             import pytest, warnings
 
             warnings.warn(DeprecationWarning("collection"))
@@ -443,9 +440,7 @@ def create_file(self, pytester: Pytester, mark="") -> None:
             {mark}
             def test_foo():
                 warnings.warn(PendingDeprecationWarning("test run"))
-        """.format(
-                mark=mark
-            )
+        """
         )
 
     @pytest.mark.parametrize("customize_filters", [True, False])
@@ -463,7 +458,7 @@ def test_shown_by_default(self, pytester: Pytester, customize_filters) -> None:
         result = pytester.runpytest_subprocess()
         result.stdout.fnmatch_lines(
             [
-                "*== %s ==*" % WARNINGS_SUMMARY_HEADER,
+                f"*== {WARNINGS_SUMMARY_HEADER} ==*",
                 "*test_shown_by_default.py:3: DeprecationWarning: collection",
                 "*test_shown_by_default.py:7: PendingDeprecationWarning: test run",
                 "* 1 passed, 2 warnings*",
@@ -494,7 +489,7 @@ def test_hidden_by_mark(self, pytester: Pytester) -> None:
         result = pytester.runpytest_subprocess()
         result.stdout.fnmatch_lines(
             [
-                "*== %s ==*" % WARNINGS_SUMMARY_HEADER,
+                f"*== {WARNINGS_SUMMARY_HEADER} ==*",
                 "*test_hidden_by_mark.py:3: DeprecationWarning: collection",
                 "* 1 passed, 1 warning*",
             ]
@@ -516,7 +511,31 @@ def test_hidden_by_system(self, pytester: Pytester, monkeypatch) -> None:
         result = pytester.runpytest_subprocess()
         assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
 
+    def test_invalid_regex_in_filterwarning(self, pytester: Pytester) -> None:
+        self.create_file(pytester)
+        pytester.makeini(
+            """
+                [pytest]
+                filterwarnings =
+                    ignore::DeprecationWarning:*
+            """
+        )
+        result = pytester.runpytest_subprocess()
+        assert result.ret == pytest.ExitCode.USAGE_ERROR
+        result.stderr.fnmatch_lines(
+            [
+                "ERROR: while parsing the following warning configuration:",
+                "",
+                "  ignore::DeprecationWarning:[*]",
+                "",
+                "This error occurred:",
+                "",
+                "Invalid regex '[*]': nothing to repeat at position 0",
+            ]
+        )
+
 
+@pytest.mark.skip("not relevant until pytest 9.0")
 @pytest.mark.parametrize("change_default", [None, "ini", "cmdline"])
 def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> None:
     """This ensures that PytestRemovedInXWarnings raised by pytest are turned into errors.
@@ -528,7 +547,7 @@ def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> No
         """
         import warnings, pytest
         def test():
-            warnings.warn(pytest.PytestRemovedIn7Warning("some warning"))
+            warnings.warn(pytest.PytestRemovedIn9Warning("some warning"))
     """
     )
     if change_default == "ini":
@@ -536,12 +555,12 @@ def test():
             """
             [pytest]
             filterwarnings =
-                ignore::pytest.PytestRemovedIn7Warning
+                ignore::pytest.PytestRemovedIn9Warning
         """
         )
 
     args = (
-        ("-Wignore::pytest.PytestRemovedIn7Warning",)
+        ("-Wignore::pytest.PytestRemovedIn9Warning",)
         if change_default == "cmdline"
         else ()
     )
@@ -556,7 +575,7 @@ def test():
 class TestAssertionWarnings:
     @staticmethod
     def assert_result_warns(result, msg) -> None:
-        result.stdout.fnmatch_lines(["*PytestAssertRewriteWarning: %s*" % msg])
+        result.stdout.fnmatch_lines([f"*PytestAssertRewriteWarning: {msg}*"])
 
     def test_tuple_warning(self, pytester: Pytester) -> None:
         pytester.makepyfile(
@@ -586,7 +605,7 @@ def test_group_warnings_by_message(pytester: Pytester) -> None:
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
         [
-            "*== %s ==*" % WARNINGS_SUMMARY_HEADER,
+            f"*== {WARNINGS_SUMMARY_HEADER} ==*",
             "test_group_warnings_by_message.py::test_foo[[]0[]]",
             "test_group_warnings_by_message.py::test_foo[[]1[]]",
             "test_group_warnings_by_message.py::test_foo[[]2[]]",
@@ -618,14 +637,14 @@ def test_group_warnings_by_message_summary(pytester: Pytester) -> None:
     result = pytester.runpytest()
     result.stdout.fnmatch_lines(
         [
-            "*== %s ==*" % WARNINGS_SUMMARY_HEADER,
+            f"*== {WARNINGS_SUMMARY_HEADER} ==*",
             "test_1.py: 21 warnings",
             "test_2.py: 1 warning",
-            "  */test_1.py:7: UserWarning: foo",
+            "  */test_1.py:10: UserWarning: foo",
             "    warnings.warn(UserWarning(msg))",
             "",
             "test_1.py: 20 warnings",
-            "  */test_1.py:7: UserWarning: bar",
+            "  */test_1.py:10: UserWarning: bar",
             "    warnings.warn(UserWarning(msg))",
             "",
             "-- Docs: *",
@@ -657,8 +676,8 @@ class TestStackLevel:
     @pytest.fixture
     def capwarn(self, pytester: Pytester):
         class CapturedWarnings:
-            captured: List[
-                Tuple[warnings.WarningMessage, Optional[Tuple[str, int, str]]]
+            captured: list[
+                tuple[warnings.WarningMessage, tuple[str, int, str] | None]
             ] = []
 
             @classmethod
@@ -707,7 +726,7 @@ def test_issue4445_preparse(self, pytester: Pytester, capwarn) -> None:
         pytester.parseconfig("--help")
 
         # with stacklevel=2 the warning should originate from config._preparse and is
-        # thrown by an errorneous conftest.py
+        # thrown by an erroneous conftest.py
         assert len(capwarn.captured) == 1
         warning, location = capwarn.captured.pop()
         file, _, func = location
@@ -773,3 +792,71 @@ def test_it():
                 "*Unknown pytest.mark.unknown*",
             ]
         )
+
+
+def test_warning_on_testpaths_not_found(pytester: Pytester) -> None:
+    # Check for warning when testpaths set, but not found by glob
+    pytester.makeini(
+        """
+        [pytest]
+        testpaths = absent
+        """
+    )
+    result = pytester.runpytest()
+    result.stdout.fnmatch_lines(
+        ["*ConfigWarning: No files were found in testpaths*", "*1 warning*"]
+    )
+
+
+def test_resource_warning(pytester: Pytester, monkeypatch: pytest.MonkeyPatch) -> None:
+    # Some platforms (notably PyPy) don't have tracemalloc.
+    # We choose to explicitly not skip this in case tracemalloc is not
+    # available, using `importorskip("tracemalloc")` for example,
+    # because we want to ensure the same code path does not break in those platforms.
+    try:
+        import tracemalloc  # noqa: F401
+
+        has_tracemalloc = True
+    except ImportError:
+        has_tracemalloc = False
+
+    # Explicitly disable PYTHONTRACEMALLOC in case pytest's test suite is running
+    # with it enabled.
+    monkeypatch.delenv("PYTHONTRACEMALLOC", raising=False)
+
+    pytester.makepyfile(
+        """
+        def open_file(p):
+            f = p.open("r", encoding="utf-8")
+            assert p.read_text() == "hello"
+
+        def test_resource_warning(tmp_path):
+            p = tmp_path.joinpath("foo.txt")
+            p.write_text("hello", encoding="utf-8")
+            open_file(p)
+        """
+    )
+    result = pytester.run(sys.executable, "-Xdev", "-m", "pytest")
+    expected_extra = (
+        [
+            "*ResourceWarning* unclosed file*",
+            "*Enable tracemalloc to get traceback where the object was allocated*",
+            "*See https* for more info.",
+        ]
+        if has_tracemalloc
+        else []
+    )
+    result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"])
+
+    monkeypatch.setenv("PYTHONTRACEMALLOC", "20")
+
+    result = pytester.run(sys.executable, "-Xdev", "-m", "pytest")
+    expected_extra = (
+        [
+            "*ResourceWarning* unclosed file*",
+            "*Object allocated at*",
+        ]
+        if has_tracemalloc
+        else []
+    )
+    result.stdout.fnmatch_lines([*expected_extra, "*1 passed*"])
diff --git a/testing/typing_checks.py b/testing/typing_checks.py
index 0a6b5ad2841..8a316580a25 100644
--- a/testing/typing_checks.py
+++ b/testing/typing_checks.py
@@ -1,9 +1,21 @@
+# mypy: allow-untyped-defs
 """File for checking typing issues.
 
 This file is not executed, it is only checked by mypy to ensure that
 none of the code triggers any mypy errors.
 """
+
+from __future__ import annotations
+
+import contextlib
+from typing import Literal
+from typing import Optional
+
+from typing_extensions import assert_type
+
 import pytest
+from pytest import MonkeyPatch
+from pytest import TestReport
 
 
 # Issue #7488.
@@ -22,3 +34,28 @@ def check_fixture_ids_callable() -> None:
 @pytest.mark.parametrize("func", [str, int], ids=lambda x: str(x.__name__))
 def check_parametrize_ids_callable(func) -> None:
     pass
+
+
+# Issue #10999.
+def check_monkeypatch_typeddict(monkeypatch: MonkeyPatch) -> None:
+    from typing import TypedDict
+
+    class Foo(TypedDict):
+        x: int
+        y: float
+
+    a: Foo = {"x": 1, "y": 3.14}
+    monkeypatch.setitem(a, "x", 2)
+    monkeypatch.delitem(a, "y")
+
+
+def check_raises_is_a_context_manager(val: bool) -> None:
+    with pytest.raises(RuntimeError) if val else contextlib.nullcontext() as excinfo:
+        pass
+    assert_type(excinfo, Optional[pytest.ExceptionInfo[RuntimeError]])
+
+
+# Issue #12941.
+def check_testreport_attributes(report: TestReport) -> None:
+    assert_type(report.when, Literal["setup", "call", "teardown"])
+    assert_type(report.location, tuple[str, Optional[int], str])
diff --git a/testing/typing_raises_group.py b/testing/typing_raises_group.py
new file mode 100644
index 00000000000..c7dd16991ac
--- /dev/null
+++ b/testing/typing_raises_group.py
@@ -0,0 +1,247 @@
+from __future__ import annotations
+
+import sys
+from typing import Callable
+from typing import Union
+
+from typing_extensions import assert_type
+
+from _pytest.main import Failed as main_Failed
+from _pytest.outcomes import Failed
+from pytest import raises
+from pytest import RaisesExc
+from pytest import RaisesGroup
+
+
+# does not work
+assert_type(raises.Exception, Failed)  # type: ignore[assert-type, attr-defined]
+
+# FIXME: these are different for some reason(?)
+assert Failed is not main_Failed  # type: ignore[comparison-overlap]
+
+if sys.version_info < (3, 11):
+    from exceptiongroup import BaseExceptionGroup
+    from exceptiongroup import ExceptionGroup
+
+# split into functions to isolate the different scopes
+
+
+def check_raisesexc_typevar_default(e: RaisesExc) -> None:
+    assert e.expected_exceptions is not None
+    _exc: type[BaseException] | tuple[type[BaseException], ...] = e.expected_exceptions
+    # this would previously pass, as the type would be `Any`
+    e.exception_type().blah()  # type: ignore
+
+
+def check_basic_contextmanager() -> None:
+    with RaisesGroup(ValueError) as e:
+        raise ExceptionGroup("foo", (ValueError(),))
+    assert_type(e.value, ExceptionGroup[ValueError])
+
+
+def check_basic_matches() -> None:
+    # check that matches gets rid of the naked ValueError in the union
+    exc: ExceptionGroup[ValueError] | ValueError = ExceptionGroup("", (ValueError(),))
+    if RaisesGroup(ValueError).matches(exc):
+        assert_type(exc, ExceptionGroup[ValueError])
+
+    # also check that BaseExceptionGroup shows up for BaseExceptions
+    if RaisesGroup(KeyboardInterrupt).matches(exc):
+        assert_type(exc, BaseExceptionGroup[KeyboardInterrupt])
+
+
+def check_matches_with_different_exception_type() -> None:
+    e: BaseExceptionGroup[KeyboardInterrupt] = BaseExceptionGroup(
+        "",
+        (KeyboardInterrupt(),),
+    )
+
+    # note: it might be tempting to have this warn.
+    # however, that isn't possible with current typing
+    if RaisesGroup(ValueError).matches(e):
+        assert_type(e, ExceptionGroup[ValueError])
+
+
+def check_raisesexc_init() -> None:
+    def check_exc(exc: BaseException) -> bool:
+        return isinstance(exc, ValueError)
+
+    # Check various combinations of constructor signatures.
+    # At least 1 arg must be provided.
+    RaisesExc()  # type: ignore
+    RaisesExc(ValueError)
+    RaisesExc(ValueError, match="regex")
+    RaisesExc(ValueError, match="regex", check=check_exc)
+    RaisesExc(match="regex")
+    RaisesExc(check=check_exc)
+    RaisesExc(ValueError, match="regex")
+    RaisesExc(match="regex", check=check_exc)
+
+    def check_filenotfound(exc: FileNotFoundError) -> bool:
+        return not exc.filename.endswith(".tmp")
+
+    # If exception_type is provided, that narrows the `check` method's argument.
+    RaisesExc(FileNotFoundError, check=check_filenotfound)
+    RaisesExc(ValueError, check=check_filenotfound)  # type: ignore
+    RaisesExc(check=check_filenotfound)  # type: ignore
+    RaisesExc(FileNotFoundError, match="regex", check=check_filenotfound)
+
+    # exceptions are pos-only
+    RaisesExc(expected_exception=ValueError)  # type: ignore
+    # match and check are kw-only
+    RaisesExc(ValueError, "regex")  # type: ignore
+
+
+def raisesgroup_check_type_narrowing() -> None:
+    """Check type narrowing on the `check` argument to `RaisesGroup`.
+    All `type: ignore`s are correctly pointing out type errors.
+    """
+
+    def handle_exc(e: BaseExceptionGroup[BaseException]) -> bool:
+        return True
+
+    def handle_kbi(e: BaseExceptionGroup[KeyboardInterrupt]) -> bool:
+        return True
+
+    def handle_value(e: BaseExceptionGroup[ValueError]) -> bool:
+        return True
+
+    RaisesGroup(BaseException, check=handle_exc)
+    RaisesGroup(BaseException, check=handle_kbi)  # type: ignore
+
+    RaisesGroup(Exception, check=handle_exc)
+    RaisesGroup(Exception, check=handle_value)  # type: ignore
+
+    RaisesGroup(KeyboardInterrupt, check=handle_exc)
+    RaisesGroup(KeyboardInterrupt, check=handle_kbi)
+    RaisesGroup(KeyboardInterrupt, check=handle_value)  # type: ignore
+
+    RaisesGroup(ValueError, check=handle_exc)
+    RaisesGroup(ValueError, check=handle_kbi)  # type: ignore
+    RaisesGroup(ValueError, check=handle_value)
+
+    RaisesGroup(ValueError, KeyboardInterrupt, check=handle_exc)
+    RaisesGroup(ValueError, KeyboardInterrupt, check=handle_kbi)  # type: ignore
+    RaisesGroup(ValueError, KeyboardInterrupt, check=handle_value)  # type: ignore
+
+
+def raisesgroup_narrow_baseexceptiongroup() -> None:
+    """Check type narrowing specifically for the container exceptiongroup."""
+
+    def handle_group(e: ExceptionGroup[Exception]) -> bool:
+        return True
+
+    def handle_group_value(e: ExceptionGroup[ValueError]) -> bool:
+        return True
+
+    RaisesGroup(ValueError, check=handle_group_value)
+
+    RaisesGroup(Exception, check=handle_group)
+
+
+def check_raisesexc_transparent() -> None:
+    with RaisesGroup(RaisesExc(ValueError)) as e:
+        ...
+    _: BaseExceptionGroup[ValueError] = e.value
+    assert_type(e.value, ExceptionGroup[ValueError])
+
+
+def check_nested_raisesgroups_contextmanager() -> None:
+    with RaisesGroup(RaisesGroup(ValueError)) as excinfo:
+        raise ExceptionGroup("foo", (ValueError(),))
+
+    _: BaseExceptionGroup[BaseExceptionGroup[ValueError]] = excinfo.value
+
+    assert_type(
+        excinfo.value,
+        ExceptionGroup[ExceptionGroup[ValueError]],
+    )
+
+    assert_type(
+        excinfo.value.exceptions[0],
+        # this union is because of how typeshed defines .exceptions
+        Union[
+            ExceptionGroup[ValueError],
+            ExceptionGroup[ExceptionGroup[ValueError]],
+        ],
+    )
+
+
+def check_nested_raisesgroups_matches() -> None:
+    """Check nested RaisesGroup with .matches"""
+    exc: ExceptionGroup[ExceptionGroup[ValueError]] = ExceptionGroup(
+        "",
+        (ExceptionGroup("", (ValueError(),)),),
+    )
+
+    if RaisesGroup(RaisesGroup(ValueError)).matches(exc):
+        assert_type(exc, ExceptionGroup[ExceptionGroup[ValueError]])
+
+
+def check_multiple_exceptions_1() -> None:
+    a = RaisesGroup(ValueError, ValueError)
+    b = RaisesGroup(RaisesExc(ValueError), RaisesExc(ValueError))
+    c = RaisesGroup(ValueError, RaisesExc(ValueError))
+
+    d: RaisesGroup[ValueError]
+    d = a
+    d = b
+    d = c
+    assert d
+
+
+def check_multiple_exceptions_2() -> None:
+    # This previously failed due to lack of covariance in the TypeVar
+    a = RaisesGroup(RaisesExc(ValueError), RaisesExc(TypeError))
+    b = RaisesGroup(RaisesExc(ValueError), TypeError)
+    c = RaisesGroup(ValueError, TypeError)
+
+    d: RaisesGroup[Exception]
+    d = a
+    d = b
+    d = c
+    assert d
+
+
+def check_raisesgroup_overloads() -> None:
+    # allow_unwrapped=True does not allow:
+    # multiple exceptions
+    RaisesGroup(ValueError, TypeError, allow_unwrapped=True)  # type: ignore
+    # nested RaisesGroup
+    RaisesGroup(RaisesGroup(ValueError), allow_unwrapped=True)  # type: ignore
+    # specifying match
+    RaisesGroup(ValueError, match="foo", allow_unwrapped=True)  # type: ignore
+    # specifying check
+    RaisesGroup(ValueError, check=bool, allow_unwrapped=True)  # type: ignore
+    # allowed variants
+    RaisesGroup(ValueError, allow_unwrapped=True)
+    RaisesGroup(ValueError, allow_unwrapped=True, flatten_subgroups=True)
+    RaisesGroup(RaisesExc(ValueError), allow_unwrapped=True)
+
+    # flatten_subgroups=True does not allow nested RaisesGroup
+    RaisesGroup(RaisesGroup(ValueError), flatten_subgroups=True)  # type: ignore
+    # but rest is plenty fine
+    RaisesGroup(ValueError, TypeError, flatten_subgroups=True)
+    RaisesGroup(ValueError, match="foo", flatten_subgroups=True)
+    RaisesGroup(ValueError, check=bool, flatten_subgroups=True)
+    RaisesGroup(ValueError, flatten_subgroups=True)
+    RaisesGroup(RaisesExc(ValueError), flatten_subgroups=True)
+
+    # if they're both false we can of course specify nested raisesgroup
+    RaisesGroup(RaisesGroup(ValueError))
+
+
+def check_triple_nested_raisesgroup() -> None:
+    with RaisesGroup(RaisesGroup(RaisesGroup(ValueError))) as e:
+        assert_type(e.value, ExceptionGroup[ExceptionGroup[ExceptionGroup[ValueError]]])
+
+
+def check_check_typing() -> None:
+    # `BaseExceptiongroup` should perhaps be `ExceptionGroup`, but close enough
+    assert_type(
+        RaisesGroup(ValueError).check,
+        Union[
+            Callable[[BaseExceptionGroup[ValueError]], bool],
+            None,
+        ],
+    )
diff --git a/tox.ini b/tox.ini
index 9d26051ebb7..850def411cb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,33 +4,60 @@ minversion = 3.20.0
 distshare = {homedir}/.tox/distshare
 envlist =
     linting
-    py36
-    py37
-    py38
     py39
     py310
+    py311
+    py312
+    py313
     pypy3
-    py37-{pexpect,xdist,unittestextras,numpy,pluggymain}
+    py39-{pexpect,xdist,unittestextras,numpy,pluggymain,pylib}
     doctesting
+    doctesting-coverage
     plugins
-    py37-freeze
+    py39-freeze
     docs
     docs-checklinks
 
+    # checks that 3.11 native ExceptionGroup works with exceptiongroup
+    # not included in CI.
+    py311-exceptiongroup
+
 
 
 [testenv]
+description =
+    run the tests
+    coverage: collecting coverage
+    exceptiongroup: against `exceptiongroup`
+    nobyte: in no-bytecode mode
+    lsof: with `--lsof` pytest CLI option
+    numpy: against `numpy`
+    pexpect: against `pexpect`
+    pluggymain: against the bleeding edge `pluggy` from Git
+    pylib: against `py` lib
+    unittestextras: against the unit test extras
+    xdist: with pytest in parallel mode
+    under `{basepython}`
+    doctesting: including doctests
 commands =
     {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:{env:_PYTEST_TOX_DEFAULT_POSARGS:}}
     doctesting: {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest
     coverage: coverage combine
     coverage: coverage report -m
-passenv = USER USERNAME COVERAGE_* PYTEST_ADDOPTS TERM SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST
+passenv =
+    COVERAGE_*
+    PYTEST_ADDOPTS
+    TERM
+    SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST
 setenv =
     _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:}
 
+    # See https://docs.python.org/3/library/io.html#io-encoding-warning
+    # If we don't enable this, neither can any of our downstream users!
+    PYTHONWARNDEFAULTENCODING=1
+
     # Configuration to run with coverage similar to CI, e.g.
-    # "tox -e py37-coverage".
+    # "tox -e py39-coverage".
     coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m
     coverage: _PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess
     coverage: COVERAGE_FILE={toxinidir}/.coverage
@@ -43,12 +70,14 @@ setenv =
     lsof: _PYTEST_TOX_POSARGS_LSOF=--lsof
 
     xdist: _PYTEST_TOX_POSARGS_XDIST=-n auto
-extras = testing
+extras = dev
 deps =
     doctesting: PyYAML
+    exceptiongroup: exceptiongroup>=1.0.0rc8
     numpy: numpy>=1.19.4
     pexpect: pexpect>=4.8.0
     pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git
+    pylib: py>=1.8.2
     unittestextras: twisted
     unittestextras: asynctest
     xdist: pytest-xdist>=2.1.0
@@ -56,53 +85,74 @@ deps =
     {env:_PYTEST_TOX_EXTRA_DEP:}
 
 [testenv:linting]
+description =
+    run pre-commit-defined linters under `{basepython}`
 skip_install = True
 basepython = python3
 deps = pre-commit>=2.9.3
 commands = pre-commit run --all-files --show-diff-on-failure {posargs:}
+setenv =
+    # pre-commit and tools it launches are not clean of this warning.
+    PYTHONWARNDEFAULTENCODING=
 
 [testenv:docs]
-basepython = python3
+description =
+    build the documentation site under \
+    `{toxinidir}{/}doc{/}en{/}_build{/}html` with `{basepython}`
+basepython = python3.12 # sync with rtd to get errors
 usedevelop = True
 deps =
     -r{toxinidir}/doc/en/requirements.txt
-    # https://github.com/twisted/towncrier/issues/340
-    towncrier<21.3.0
 commands =
-    python scripts/towncrier-draft-to-file.py
-    # the '-t changelog_towncrier_draft' tags makes sphinx include the draft
-    # changelog in the docs; this does not happen on ReadTheDocs because it uses
-    # the standard sphinx command so the 'changelog_towncrier_draft' is never set there
-    sphinx-build -W --keep-going -b html doc/en doc/en/_build/html -t changelog_towncrier_draft {posargs:}
+    sphinx-build \
+      -j auto \
+      -W --keep-going \
+      -b html doc/en doc/en/_build/html \
+      {posargs:}
+setenv =
+    # Sphinx is not clean of this warning.
+    PYTHONWARNDEFAULTENCODING=
 
 [testenv:docs-checklinks]
+description =
+    check the links in the documentation with `{basepython}`
 basepython = python3
 usedevelop = True
 changedir = doc/en
 deps = -r{toxinidir}/doc/en/requirements.txt
 commands =
     sphinx-build -W -q --keep-going -b linkcheck . _build
+setenv =
+    # Sphinx is not clean of this warning.
+    PYTHONWARNDEFAULTENCODING=
 
 [testenv:regen]
+description =
+    regenerate documentation examples under `{basepython}`
 changedir = doc/en
 basepython = python3
-passenv = SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST
+passenv =
+    SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST
 deps =
-    dataclasses
     PyYAML
     regendoc>=0.8.1
     sphinx
-whitelist_externals =
+allowlist_externals =
     make
 commands =
     make regen
+setenv =
+    # We don't want this warning to reach regen output.
+    PYTHONWARNDEFAULTENCODING=
 
 [testenv:plugins]
+description =
+    run reverse dependency testing against pytest plugins under `{basepython}`
 # use latest versions of all plugins, including pre-releases
 pip_pre=true
-# use latest pip and new dependency resolver (#7783)
+# use latest pip to get new dependency resolver (#7783)
 download=true
-install_command=python -m pip --use-feature=2020-resolver install {opts} {packages}
+install_command=python -m pip install {opts} {packages}
 changedir = testing/plugins_integration
 deps = -rtesting/plugins_integration/requirements.txt
 setenv =
@@ -113,7 +163,7 @@ commands =
     pytest --cov=. simple_integration.py
     pytest --ds=django_settings simple_integration.py
     pytest --html=simple.html simple_integration.py
-    pytest --reruns 5 simple_integration.py
+    pytest --reruns 5 simple_integration.py pytest_rerunfailures_integration.py
     pytest pytest_anyio_integration.py
     pytest pytest_asyncio_integration.py
     pytest pytest_mock_integration.py
@@ -121,7 +171,9 @@ commands =
     pytest pytest_twisted_integration.py
     pytest simple_integration.py --force-sugar --flakes
 
-[testenv:py37-freeze]
+[testenv:py39-freeze]
+description =
+    test pytest frozen with `pyinstaller` under `{basepython}`
 changedir = testing/freeze
 deps =
     pyinstaller
@@ -130,54 +182,27 @@ commands =
     {envpython} tox_run.py
 
 [testenv:release]
-decription = do a release, required posarg of the version number
+description = do a release, required posarg of the version number
 basepython = python3
 usedevelop = True
 passenv = *
 deps =
     colorama
-    github3.py
     pre-commit>=2.9.3
-    wheel
-    # https://github.com/twisted/towncrier/issues/340
-    towncrier<21.3.0
+    towncrier
 commands = python scripts/release.py {posargs}
 
 [testenv:prepare-release-pr]
-decription = prepare a release PR from a manual trigger in GitHub actions
+description = prepare a release PR from a manual trigger in GitHub actions
 usedevelop = {[testenv:release]usedevelop}
 passenv = {[testenv:release]passenv}
 deps = {[testenv:release]deps}
 commands = python scripts/prepare-release-pr.py {posargs}
 
-[testenv:publish-gh-release-notes]
-description = create GitHub release after deployment
+[testenv:generate-gh-release-notes]
+description = generate release notes that can be published as GitHub Release
 basepython = python3
 usedevelop = True
-passenv = GH_RELEASE_NOTES_TOKEN GITHUB_REF GITHUB_REPOSITORY
 deps =
-    github3.py
     pypandoc
-commands = python scripts/publish-gh-release-notes.py {posargs}
-
-[flake8]
-max-line-length = 120
-extend-ignore =
-    ; whitespace before ':'
-    E203
-    ; Missing Docstrings
-    D100,D101,D102,D103,D104,D105,D106,D107
-    ; Whitespace Issues
-    D202,D203,D204,D205,D209,D213
-    ; Quotes Issues
-    D302
-    ; Docstring Content Issues
-    D400,D401,D401,D402,D405,D406,D407,D408,D409,D410,D411,D412,D413,D414,D415,D416,D417
-
-
-[isort]
-; This config mimics what reorder-python-imports does.
-force_single_line = 1
-known_localfolder = pytest,_pytest
-known_third_party = test_source,test_excinfo
-force_alphabetical_sort_within_sections = 1
+commands = python scripts/generate-gh-release-notes.py {posargs}