diff --git a/changelog/7259.deprecation.rst b/changelog/7259.deprecation.rst
new file mode 100644
index 00000000000..c0307740d55
--- /dev/null
+++ b/changelog/7259.deprecation.rst
@@ -0,0 +1,3 @@
+``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.
diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst
index 775bc1958a3..b82dd8521e8 100644
--- a/doc/en/deprecations.rst
+++ b/doc/en/deprecations.rst
@@ -18,17 +18,40 @@ 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>`.
 
+.. _node-ctor-fspath-deprecation:
+
+``fspath`` argument for Node constructors replaced with ``pathlib.Path``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. deprecated:: 7.0
+
+In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
+the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
+:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
+is now deprecated.
+
+Plugins which construct nodes should pass the ``path`` argument, of type
+:class:`pathlib.Path`, instead of the ``fspath`` argument.
+
+Plugins which implement custom items and collectors are encouraged to replace
+``py.path.local`` ``fspath`` parameters with ``pathlib.Path`` parameters, and
+drop any other usage of the ``py`` library if possible.
+
+
+.. _legacy-path-hooks-deprecated:
 
 ``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-In order to support the transition to :mod:`pathlib`, the following hooks now receive additional arguments:
+.. deprecated:: 7.0
+
+In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
 
-*  :func:`pytest_ignore_collect(fspath: pathlib.Path) <_pytest.hookspec.pytest_ignore_collect>`
-*  :func:`pytest_collect_file(fspath: pathlib.Path) <_pytest.hookspec.pytest_collect_file>`
-*  :func:`pytest_pycollect_makemodule(fspath: pathlib.Path) <_pytest.hookspec.pytest_pycollect_makemodule>`
-*  :func:`pytest_report_header(startpath: pathlib.Path) <_pytest.hookspec.pytest_report_header>`
-*  :func:`pytest_report_collectionfinish(startpath: pathlib.Path) <_pytest.hookspec.pytest_report_collectionfinish>`
+*  :func:`pytest_ignore_collect(fspath: pathlib.Path) <_pytest.hookspec.pytest_ignore_collect>` instead of ``path``
+*  :func:`pytest_collect_file(fspath: pathlib.Path) <_pytest.hookspec.pytest_collect_file>` instead of ``path``
+*  :func:`pytest_pycollect_makemodule(fspath: pathlib.Path) <_pytest.hookspec.pytest_pycollect_makemodule>` instead of ``path``
+*  :func:`pytest_report_header(startpath: pathlib.Path) <_pytest.hookspec.pytest_report_header>` instead of ``startdir``
+*  :func:`pytest_report_collectionfinish(startpath: pathlib.Path) <_pytest.hookspec.pytest_report_collectionfinish>` instead of ``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.
 
@@ -59,7 +82,7 @@ Implement the :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load
 Diamond inheritance between :class:`pytest.File` and :class:`pytest.Item`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. deprecated:: 6.3
+.. deprecated:: 7.0
 
 Inheriting from both Item and file at once has never been supported officially,
 however some plugins providing linting/code analysis have been using this as a hack.
@@ -86,7 +109,7 @@ scheduled for removal in pytest 7 (deprecated since pytest 2.4.0):
 Raising ``unittest.SkipTest`` during collection
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-.. deprecated:: 6.3
+.. deprecated:: 7.0
 
 Raising :class:`unittest.SkipTest` to skip collection of tests during the
 pytest collection phase is deprecated. Use :func:`pytest.skip` instead.
diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst
index ae76b5bce2f..1a3d1873c41 100644
--- a/doc/en/how-to/capture-warnings.rst
+++ b/doc/en/how-to/capture-warnings.rst
@@ -268,7 +268,7 @@ argument ``match`` to assert that the exception matches a text or regex::
     ...     warnings.warn("this is not here", UserWarning)
     Traceback (most recent call last):
       ...
-    Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
+    Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
 
 You can also call :func:`pytest.warns` on a function or code string:
 
diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py
index 78cec709337..78edf9ac5c6 100755
--- a/src/_pytest/cacheprovider.py
+++ b/src/_pytest/cacheprovider.py
@@ -128,7 +128,7 @@ def mkdir(self, name: str) -> Path:
         it to manage files to e.g. store/retrieve database dumps across test
         sessions.
 
-        .. versionadded:: 6.3
+        .. versionadded:: 7.0
 
         :param name:
             Must be a string not containing a ``/`` separator.
diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py
index b3aff258a78..b0bb3f168ff 100644
--- a/src/_pytest/config/argparsing.py
+++ b/src/_pytest/config/argparsing.py
@@ -185,7 +185,7 @@ def addini(
                 * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell
                 * ``pathlist``: a list of ``py.path``, separated as in a shell
 
-            .. versionadded:: 6.3
+            .. versionadded:: 7.0
                 The ``paths`` variable type.
 
             Defaults to ``string`` if ``None`` or not passed.
diff --git a/src/_pytest/config/compat.py b/src/_pytest/config/compat.py
index c93f3738d4f..731641ddebf 100644
--- a/src/_pytest/config/compat.py
+++ b/src/_pytest/config/compat.py
@@ -4,8 +4,9 @@
 from typing import Optional
 
 from ..compat import LEGACY_PATH
+from ..compat import legacy_path
 from ..deprecated import HOOK_LEGACY_PATH_ARG
-from _pytest.nodes import _imply_path
+from _pytest.nodes import _check_path
 
 # hookname: (Path, LEGACY_PATH)
 imply_paths_hooks = {
@@ -52,7 +53,15 @@ def fixed_hook(**kw):
                         ),
                         stacklevel=2,
                     )
-                path_value, fspath_value = _imply_path(path_value, fspath_value)
+                if path_value is not None:
+                    if fspath_value is not None:
+                        _check_path(path_value, fspath_value)
+                    else:
+                        fspath_value = legacy_path(path_value)
+                else:
+                    assert fspath_value is not None
+                    path_value = Path(fspath_value)
+
                 kw[path_var] = path_value
                 kw[fspath_var] = fspath_value
                 return hook(**kw)
diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py
index 0c5db6d5335..452128d07b9 100644
--- a/src/_pytest/deprecated.py
+++ b/src/_pytest/deprecated.py
@@ -101,6 +101,14 @@
     "#py-path-local-arguments-for-hooks-replaced-with-pathlib-path",
 )
 
+NODE_CTOR_FSPATH_ARG = UnformattedWarning(
+    PytestDeprecationWarning,
+    "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 = PytestDeprecationWarning(
     "Passing None to catch any warning has been deprecated, pass no arguments instead:\n"
     " Replace pytest.warns(None) by simply pytest.warns()."
diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py
index 29a713b007d..e3cfc07186e 100644
--- a/src/_pytest/hookspec.py
+++ b/src/_pytest/hookspec.py
@@ -272,12 +272,13 @@ def pytest_ignore_collect(
     Stops at first non-None result, see :ref:`firstresult`.
 
     :param pathlib.Path fspath: The path to analyze.
-    :param LEGACY_PATH path: The path to analyze.
+    :param LEGACY_PATH path: The path to analyze (deprecated).
     :param pytest.Config config: The pytest config object.
 
-    .. versionchanged:: 6.3.0
+    .. versionchanged:: 7.0.0
         The ``fspath`` parameter was added as a :class:`pathlib.Path`
-        equivalent of the ``path`` parameter.
+        equivalent of the ``path`` parameter. The ``path`` parameter
+        has been deprecated.
     """
 
 
@@ -289,11 +290,12 @@ def pytest_collect_file(
     The new node needs to have the specified ``parent`` as a parent.
 
     :param pathlib.Path fspath: The path to analyze.
-    :param LEGACY_PATH path: The path to collect.
+    :param LEGACY_PATH path: The path to collect (deprecated).
 
-    .. versionchanged:: 6.3.0
+    .. versionchanged:: 7.0.0
         The ``fspath`` parameter was added as a :class:`pathlib.Path`
-        equivalent of the ``path`` parameter.
+        equivalent of the ``path`` parameter. The ``path`` parameter
+        has been deprecated.
     """
 
 
@@ -345,11 +347,13 @@ def pytest_pycollect_makemodule(
     Stops at first non-None result, see :ref:`firstresult`.
 
     :param pathlib.Path fspath: The path of the module to collect.
-    :param legacy_path path: The path of the module to collect.
+    :param LEGACY_PATH path: The path of the module to collect (deprecated).
 
-    .. versionchanged:: 6.3.0
+    .. versionchanged:: 7.0.0
         The ``fspath`` parameter was added as a :class:`pathlib.Path`
         equivalent of the ``path`` parameter.
+
+        The ``path`` parameter has been deprecated in favor of ``fspath``.
     """
 
 
@@ -674,7 +678,7 @@ def pytest_report_header(
 
     :param pytest.Config config: The pytest config object.
     :param Path startpath: The starting dir.
-    :param LEGACY_PATH startdir: The starting dir.
+    :param LEGACY_PATH startdir: The starting dir (deprecated).
 
     .. note::
 
@@ -689,9 +693,10 @@ def pytest_report_header(
         files situated at the tests root directory due to how pytest
         :ref:`discovers plugins during startup <pluginorder>`.
 
-    .. versionchanged:: 6.3.0
+    .. versionchanged:: 7.0.0
         The ``startpath`` parameter was added as a :class:`pathlib.Path`
-        equivalent of the ``startdir`` parameter.
+        equivalent of the ``startdir`` parameter. The ``startdir`` parameter
+        has been deprecated.
     """
 
 
@@ -709,8 +714,8 @@ def pytest_report_collectionfinish(
     .. versionadded:: 3.2
 
     :param pytest.Config config: The pytest config object.
-    :param Path startpath: The starting path.
-    :param LEGACY_PATH startdir: The starting dir.
+    :param Path startpath: The starting dir.
+    :param LEGACY_PATH startdir: The starting dir (deprecated).
     :param items: List of pytest items that are going to be executed; this list should not be modified.
 
     .. note::
@@ -720,9 +725,10 @@ def pytest_report_collectionfinish(
         If you want to have your line(s) displayed first, use
         :ref:`trylast=True <plugin-hookorder>`.
 
-    .. versionchanged:: 6.3.0
+    .. versionchanged:: 7.0.0
         The ``startpath`` parameter was added as a :class:`pathlib.Path`
-        equivalent of the ``startdir`` parameter.
+        equivalent of the ``startdir`` parameter. The ``startdir`` parameter
+        has been deprecated.
     """
 
 
diff --git a/src/_pytest/main.py b/src/_pytest/main.py
index cfdc091e1d6..c482224094a 100644
--- a/src/_pytest/main.py
+++ b/src/_pytest/main.py
@@ -500,7 +500,7 @@ def __repr__(self) -> str:
     def startpath(self) -> Path:
         """The path from which pytest was invoked.
 
-        .. versionadded:: 6.3.0
+        .. versionadded:: 7.0.0
         """
         return self.config.invocation_params.dir
 
diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py
index 3b70e111789..05cf01fc6c4 100644
--- a/src/_pytest/nodes.py
+++ b/src/_pytest/nodes.py
@@ -28,6 +28,7 @@
 from _pytest.config import Config
 from _pytest.config import ConftestImportFailure
 from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
+from _pytest.deprecated import NODE_CTOR_FSPATH_ARG
 from _pytest.mark.structures import Mark
 from _pytest.mark.structures import MarkDecorator
 from _pytest.mark.structures import NodeKeywords
@@ -102,22 +103,17 @@ def _check_path(path: Path, fspath: LEGACY_PATH) -> None:
 
 
 def _imply_path(
-    path: Optional[Path], fspath: Optional[LEGACY_PATH]
-) -> Tuple[Path, LEGACY_PATH]:
-    if path is not None:
-        if fspath is not None:
-            _check_path(path, fspath)
-        else:
-            fspath = legacy_path(path)
-        return path, fspath
-    else:
-        assert fspath is not None
-        return Path(fspath), fspath
-
-
-# Optimization: use _imply_path_only over _imply_path when only need Path.
-# This is to avoid `legacy_path(path)` which is surprisingly heavy.
-def _imply_path_only(path: Optional[Path], fspath: Optional[LEGACY_PATH]) -> Path:
+    node_type: Type["Node"],
+    path: Optional[Path],
+    fspath: Optional[LEGACY_PATH],
+) -> Path:
+    if fspath is not None:
+        warnings.warn(
+            NODE_CTOR_FSPATH_ARG.format(
+                node_type_name=node_type.__name__,
+            ),
+            stacklevel=3,
+        )
     if path is not None:
         if fspath is not None:
             _check_path(path, fspath)
@@ -210,9 +206,9 @@ def __init__(
             self.session = parent.session
 
         #: Filesystem path where this node was collected from (can be None).
-        self.path = _imply_path_only(
-            path or getattr(parent, "path", None), fspath=fspath
-        )
+        if path is None and fspath is None:
+            path = getattr(parent, "path", None)
+        self.path = _imply_path(type(self), path, fspath=fspath)
 
         # The explicit annotation is to avoid publicly exposing NodeKeywords.
         #: Keywords/markers collected from all scopes.
@@ -589,7 +585,7 @@ def __init__(
                 assert path is None
                 path = path_or_parent
 
-        path = _imply_path_only(path, fspath=fspath)
+        path = _imply_path(type(self), path, fspath=fspath)
         if name is None:
             name = path.name
             if parent is not None and parent.path != path:
@@ -634,7 +630,6 @@ def from_parent(
         **kw,
     ):
         """The public constructor."""
-        path, fspath = _imply_path(path, fspath=fspath)
         return super().from_parent(parent=parent, fspath=fspath, path=path, **kw)
 
     def gethookproxy(self, fspath: "os.PathLike[str]"):
diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py
index 950d853f51d..175b571a80c 100644
--- a/src/_pytest/recwarn.py
+++ b/src/_pytest/recwarn.py
@@ -136,7 +136,7 @@ def warns(
         ...     warnings.warn("this is not here", UserWarning)
         Traceback (most recent call last):
           ...
-        Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
+        Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
 
     """
     __tracebackhide__ = True
@@ -274,7 +274,7 @@ def __exit__(
                 if not any(issubclass(r.category, self.expected_warning) for r in self):
                     __tracebackhide__ = True
                     fail(
-                        "DID NOT WARN. No warnings of type {} was emitted. "
+                        "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]
                         )
@@ -287,7 +287,7 @@ def __exit__(
                     else:
                         fail(
                             "DID NOT WARN. No warnings of type {} matching"
-                            " ('{}') was emitted. The list of emitted warnings"
+                            " ('{}') were emitted. The list of emitted warnings"
                             " is: {}.".format(
                                 self.expected_warning,
                                 self.match_expr,
diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py
index bf796a3396f..0f5e483ce38 100644
--- a/testing/deprecated_test.py
+++ b/testing/deprecated_test.py
@@ -215,3 +215,16 @@ def pytest_cmdline_preparse(config, args):
             "*Please use pytest_load_initial_conftests hook instead.*",
         ]
     )
+
+
+def test_node_ctor_fspath_argument_is_deprecated(pytester: Pytester) -> None:
+    mod = pytester.getmodulecol("")
+
+    with pytest.warns(
+        pytest.PytestDeprecationWarning,
+        match=re.escape("The (fspath: py.path.local) argument to File is deprecated."),
+    ):
+        pytest.File.from_parent(
+            parent=mod.parent,
+            fspath=legacy_path("bla"),
+        )
diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt
index 8ef321fd3f8..89e8b0890c5 100644
--- a/testing/plugins_integration/requirements.txt
+++ b/testing/plugins_integration/requirements.txt
@@ -4,7 +4,7 @@ pytest-asyncio==0.16.0
 pytest-bdd==4.1.0
 pytest-cov==3.0.0
 pytest-django==4.4.0
-pytest-flakes==4.0.3
+pytest-flakes==4.0.4
 pytest-html==3.1.1
 pytest-mock==3.6.1
 pytest-rerunfailures==10.2
diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py
index b82fd9a865b..d3f218f1660 100644
--- a/testing/test_recwarn.py
+++ b/testing/test_recwarn.py
@@ -263,7 +263,7 @@ def test_as_contextmanager(self) -> None:
             with pytest.warns(RuntimeWarning):
                 warnings.warn("user", UserWarning)
         excinfo.match(
-            r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) was emitted. "
+            r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted. "
             r"The list of emitted warnings is: \[UserWarning\('user',?\)\]."
         )
 
@@ -271,7 +271,7 @@ def test_as_contextmanager(self) -> None:
             with pytest.warns(UserWarning):
                 warnings.warn("runtime", RuntimeWarning)
         excinfo.match(
-            r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. "
+            r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. "
             r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\]."
         )
 
@@ -279,7 +279,7 @@ def test_as_contextmanager(self) -> None:
             with pytest.warns(UserWarning):
                 pass
         excinfo.match(
-            r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. "
+            r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. "
             r"The list of emitted warnings is: \[\]."
         )
 
@@ -290,7 +290,7 @@ def test_as_contextmanager(self) -> None:
                 warnings.warn("import", ImportWarning)
 
         message_template = (
-            "DID NOT WARN. No warnings of type {0} was emitted. "
+            "DID NOT WARN. No warnings of type {0} were emitted. "
             "The list of emitted warnings is: {1}."
         )
         excinfo.match(