Repository: aristanetworks/anta
Pull request #1188 (2025-05-05 08:21:18)

feat(anta.tests): Added VerifyPhysicalInterfacesCounterDetails test
# Description
Added test VerifyInterfacesCounters to support all interface errors with expected threshold

Added test changes -
 **1. interfaces_errors_and_discards_test.py
    2. interfaces_mgmt_only_test.py**

# Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have run pre-commit for code linting and typing (`pre-commit run`)
- [x] I have made corresponding changes to the documentation
- [x] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes (`tox -e testenv`)


sonarqubecloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list) (required ≥ 80%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list) (required ≥ 80%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [2 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


github-actions[bot]: This pull request has conflicts, please resolve those before we can evaluate the pull request.
github-actions[bot]: This pull request has conflicts, please resolve those before we can evaluate the pull request.
github-actions[bot]: Conflicts have been resolved. A maintainer will review the pull request shortly.
sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list) (required ≥ 80%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list) (required ≥ 80%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


github-actions[bot]: Conflicts have been resolved. A maintainer will review the pull request shortly.
sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


github-actions[bot]: Conflicts have been resolved. A maintainer will review the pull request shortly.
sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [100.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [94.6% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [97.7% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [98.2% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)


sonarqubecloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=aristanetworks_anta&pullRequest=1188&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=aristanetworks_anta&pullRequest=1188&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [85.5% Coverage on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=aristanetworks_anta&pullRequest=1188&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=aristanetworks_anta&pullRequest=1188)



Patch:
diff --git a/anta/custom_types.py b/anta/custom_types.py
index eb9543f8..05d36d10 100644
--- a/anta/custom_types.py
+++ b/anta/custom_types.py
@@ -219,10 +219,15 @@ Interface = Annotated[
 ]
 EthernetInterface = Annotated[
     str,
-    Field(pattern=r"^Ethernet[0-9]+(\/[0-9]+)*$"),
+    Field(pattern=r"^Ethernet\d+(?:/\d+){0,2}$"),
     BeforeValidator(interface_autocomplete),
     BeforeValidator(interface_case_sensitivity),
 ]
+ManagementInterface = Annotated[
+    str,
+    Field(pattern=r"^Management\d+(?:/\d+){0,2}$"),
+    BeforeValidator(interface_case_sensitivity),
+]
 VxlanSrcIntf = Annotated[
     str,
     Field(pattern=REGEXP_TYPE_VXLAN_SRC_INTERFACE),
diff --git a/anta/tools.py b/anta/tools.py
index 875a4afc..54a62165 100644
--- a/anta/tools.py
+++ b/anta/tools.py
@@ -9,6 +9,7 @@ import cProfile
 import os
 import pstats
 import re
+from datetime import datetime, timezone
 from functools import wraps
 from time import perf_counter
 from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
@@ -519,3 +520,39 @@ def get_value_by_range_key(dictionary: dict[str, Any], key: str, default: Any =
             return detail
 
     return default
+
+
+def time_ago(timestamp: float) -> str:
+    """Return a human-readable string representing the time elapsed since a given timestamp.
+
+    Parameters
+    ----------
+    timestamp
+        A POSIX timestamp (a float representing seconds since the epoch).
+
+    Returns
+    -------
+    str
+        A string describing the elapsed time.
+    """
+    now = datetime.now(timezone.utc)
+    then = datetime.fromtimestamp(timestamp, tz=timezone.utc)
+    delta = now - then
+
+    if delta.days > 0:
+        return f"{delta.days} day{'s' if delta.days > 1 else ''}"
+
+    hours, remainder = divmod(delta.seconds, 3600)
+    minutes, _ = divmod(remainder, 60)
+
+    if hours > 0:
+        hour_str = f"{hours} hour{'s' if hours > 1 else ''}"
+        if minutes > 0:
+            minute_str = f"{minutes} minute{'s' if minutes > 1 else ''}"
+            return f"{hour_str} and {minute_str}"
+        return hour_str
+
+    if minutes > 0:
+        return f"{minutes} minute{'s' if minutes > 1 else ''}"
+
+    return "less than a minute"


Test patch:
diff --git a/anta/tests/interfaces.py b/anta/tests/interfaces.py
index 16249110..3c3c2921 100644
--- a/anta/tests/interfaces.py
+++ b/anta/tests/interfaces.py
@@ -7,16 +7,25 @@
 # mypy: disable-error-code=attr-defined
 from __future__ import annotations
 
-from typing import Any, ClassVar, TypeVar
+import re
+from typing import TYPE_CHECKING, Any, ClassVar, TypeVar
 
-from pydantic import Field, field_validator
+from pydantic import Field, field_validator, model_validator
 from pydantic_extra_types.mac_address import MacAddress
 
-from anta.custom_types import Interface, InterfaceType, Percent, PortChannelInterface, PositiveInteger
+from anta.custom_types import EthernetInterface, Interface, InterfaceType, ManagementInterface, Percent, PortChannelInterface, PositiveInteger
 from anta.decorators import skip_on_platforms
 from anta.input_models.interfaces import InterfaceDetail, InterfaceState
 from anta.models import AntaCommand, AntaTemplate, AntaTest
-from anta.tools import custom_division, get_item, get_value, get_value_by_range_key, is_interface_ignored
+from anta.tools import custom_division, get_item, get_value, get_value_by_range_key, is_interface_ignored, time_ago
+
+if TYPE_CHECKING:
+    import sys
+
+    if sys.version_info >= (3, 11):
+        from typing import Self
+    else:
+        from typing_extensions import Self
 
 BPS_GBPS_CONVERSIONS = 1000000000
 
@@ -1197,3 +1206,139 @@ class VerifyInterfacesTridentCounters(AntaTest):
                     self.result.is_failure(
                         f"Interface: {interface} Error Counter: {counter_name} - Threshold exceeded - Expected: {expected_counter_value} Actual: {error_counter}"
                     )
+
+
+class VerifyPhysicalInterfacesCounterDetails(AntaTest):
+    """Verifies the physical interfaces counter details.
+
+    Expected Results
+    ----------------
+    * Success: The test will pass if all tested interfaces have counters and link status changes at or below the defined thresholds.
+    * Failure: The test will fail if any tested interface has one or more counters or a link status changes count that exceeds its defined threshold.
+
+    Examples
+    --------
+    ```yaml
+    anta.tests.interfaces:
+      - VerifyPhysicalInterfacesCounterDetails:
+          interfaces:  # Optionally target specific interfaces
+            - Ethernet1/1
+            - Ethernet2/1
+          ignored_interfaces:  # OR ignore specific interfaces
+            - Management0
+          counter_threshold: 10
+          link_status_changes_threshold: 100
+    ```
+    """
+
+    categories: ClassVar[list[str]] = ["interfaces"]
+    commands: ClassVar[list[AntaCommand | AntaTemplate]] = [AntaCommand(command="show interfaces", revision=1)]
+
+    class Input(AntaTest.Input):
+        """Input model for the VerifyPhysicalInterfacesCounterDetails test."""
+
+        interfaces: list[EthernetInterface | ManagementInterface] | None = None
+        """A list of Ethernet or Management interfaces to be tested.
+        If not provided, all Ethernet or Management interfaces (excluding any in `ignored_interfaces`) are tested."""
+        ignored_interfaces: list[EthernetInterface | ManagementInterface] | None = None
+        """A list of Ethernet or Management interfaces to ignore."""
+        counters_threshold: PositiveInteger = 0
+        """The maximum acceptable value for each verified counter."""
+        link_status_changes_threshold: PositiveInteger = 100
+        """The maximum acceptable number of link status changes."""
+
+        @model_validator(mode="after")
+        def validate_duplicate_interfaces(self) -> Self:
+            """Validate that no interface exists in both interfaces and ignored_interfaces simultaneously."""
+            redundant_interfaces = []
+            if self.interfaces and self.ignored_interfaces:
+                redundant_interfaces = list(set(self.interfaces) & set(self.ignored_interfaces))
+            if redundant_interfaces:
+                msg = f"Interface(s) {', '.join(redundant_interfaces)} are present in both 'interfaces' and 'ignored_interfaces' lists"
+                raise ValueError(msg)
+            return self
+
+    @AntaTest.anta_test
+    def test(self) -> None:
+        """Main test function for VerifyPhysicalInterfacesCounterDetails."""
+        self.result.is_success()
+        command_output = self.instance_commands[0].json_output
+        interfaces_to_check = self._get_interfaces_to_check(command_output)
+
+        for interface, intf_details in interfaces_to_check.items():
+            # Verification is skipped if the interface is in the ignored interfaces list
+            if is_interface_ignored(interface, self.inputs.ignored_interfaces):
+                continue
+
+            # Verification is skipped if the interface is a subinterface or is not an EthernetX or ManagementX interface
+            if re.fullmatch(r"^(Ethernet|Management)\d+(?:/\d+){0,2}$", interface) is None:
+                continue
+
+            # Verification is skipped if interface counters are not found
+            if not (interface_counters := intf_details.get("interfaceCounters", {})):
+                self.logger.debug("Interface: %s has been ignored as interface counters not found", interface)
+                continue
+
+            # Retrieve the interface failure message summary
+            interface_failure_message_summary = self._generate_interface_failure_message_summary(interface, intf_details)
+
+            # Verify the link status changes
+            if (act_link_status_changes := interface_counters["linkStatusChanges"]) > self.inputs.link_status_changes_threshold:
+                self.result.is_failure(
+                    f"{interface_failure_message_summary} - Link status changes count above threshold -"
+                    f" Expected: < {self.inputs.link_status_changes_threshold} Actual: {act_link_status_changes}"
+                )
+
+            # Verify interface counters
+            self._verify_interface_counters(interface_counters, interface_failure_message_summary)
+
+    def _get_interfaces_to_check(self, intf_details: dict[str, Any]) -> dict[str, Any]:
+        """Get the interfaces to check and their corresponding details based on the provided input interfaces."""
+        # Prepare the dictionary of interfaces to check
+        interfaces_to_check: dict[str, Any] = {}
+        if self.inputs.interfaces:
+            for intf_name in self.inputs.interfaces:
+                if (intf_detail := get_value(intf_details["interfaces"], intf_name, separator="..")) is None:
+                    self.result.is_failure(f"Interface: {intf_name} - Not found")
+                    continue
+                interfaces_to_check[intf_name] = intf_detail
+        else:
+            # If no specific interfaces are given, use all interfaces
+            interfaces_to_check = intf_details["interfaces"]
+        return interfaces_to_check
+
+    def _generate_interface_failure_message_summary(self, interface: str, intf_details: dict[str, Any]) -> str:
+        """Generate an interface failure message summary from the provided interface details."""
+        interface_summary = f"Interface: {interface}"
+        interface_is_up = intf_details["lineProtocolStatus"] == "up" and intf_details["interfaceStatus"] == "connected"
+        if intf_description := intf_details.get("description"):
+            interface_summary += f" Description: {intf_description}"
+        if (intf_timestamp := intf_details.get("lastStatusChangeTimestamp")) is not None:
+            last_status_change = time_ago(intf_timestamp)
+            uptime_or_downtime = " Uptime" if interface_is_up else " Downtime"
+            interface_summary += f"{uptime_or_downtime}: {last_status_change}"
+        return interface_summary
+
+    def _verify_interface_counters(self, interface_counters: dict[str, Any], interface_failure_message_summary: str) -> None:
+        """Verify counters of an interface."""
+        counters_to_verify = [
+            {"counter_key": "inDiscards", "counter_name": "Input discards"},
+            {"counter_key": "outDiscards", "counter_name": "Output discards"},
+            {"counter_key": "totalInErrors", "counter_name": "Input errors"},
+            {"counter_key": "totalOutErrors", "counter_name": "Output errors"},
+            {"counter_key": "inputErrorsDetail.runtFrames", "counter_name": "Runt frames"},
+            {"counter_key": "inputErrorsDetail.giantFrames", "counter_name": "Giant frames"},
+            {"counter_key": "inputErrorsDetail.fcsErrors", "counter_name": "CRC errors"},
+            {"counter_key": "inputErrorsDetail.alignmentErrors", "counter_name": "Alignment errors"},
+            {"counter_key": "inputErrorsDetail.symbolErrors", "counter_name": "Symbol errors"},
+            {"counter_key": "outputErrorsDetail.collisions", "counter_name": "Collisions"},
+            {"counter_key": "outputErrorsDetail.lateCollisions", "counter_name": "Late collisions"},
+            {"counter_key": "outputErrorsDetail.deferredTransmissions", "counter_name": "Deferred transmissions"},
+        ]
+        for counter in counters_to_verify:
+            counter_value = get_value(interface_counters, counter["counter_key"])
+            expected_counter_value = "0" if not self.inputs.counters_threshold else f"< {self.inputs.counters_threshold}"
+            if counter_value > self.inputs.counters_threshold:
+                self.result.is_failure(
+                    f"{interface_failure_message_summary} - {counter['counter_name']} above threshold - Expected: {expected_counter_value} Actual: {counter_value}"
+                )
diff --git a/examples/tests.yaml b/examples/tests.yaml
index e10663d5..88910e4c 100644
--- a/examples/tests.yaml
+++ b/examples/tests.yaml
@@ -379,6 +379,15 @@ anta.tests.interfaces:
   - VerifyLoopbackCount:
       # Verifies the number of loopback interfaces and their status.
       number: 3
+  - VerifyPhysicalInterfacesCounterDetails:
+      # Verifies the physical interfaces counter details.
+      interfaces:  # Optionally target specific interfaces
+        - Ethernet1/1
+        - Ethernet2/1
+      ignored_interfaces:  # OR ignore specific interfaces
+        - Management0
+      counter_threshold: 10
+      link_status_changes_threshold: 100
   - VerifyPortChannels:
       # Verifies there are no inactive ports in port channels.
       ignored_interfaces:
diff --git a/tests/units/anta_tests/test_interfaces.py b/tests/units/anta_tests/test_interfaces.py
index 13a0ff3a..023d729e 100644
--- a/tests/units/anta_tests/test_interfaces.py
+++ b/tests/units/anta_tests/test_interfaces.py
@@ -7,6 +7,7 @@
 from __future__ import annotations
 
 import sys
+from datetime import datetime, timedelta, timezone
 from typing import TYPE_CHECKING, Any
 
 from anta.models import AntaTest
@@ -28,6 +29,7 @@ from anta.tests.interfaces import (
     VerifyL3MTU,
     VerifyLACPInterfacesStatus,
     VerifyLoopbackCount,
+    VerifyPhysicalInterfacesCounterDetails,
     VerifyPortChannels,
     VerifyStormControlDrops,
     VerifySVI,
@@ -62,6 +64,12 @@ def create_status_data(*interfaces_with_status: tuple[str, str, float]) -> dict[
     return data
 
 
+# Mock current time to maintain test VerifyPhysicalInterfacesCounterDetails stability
+now = datetime.now(timezone.utc)
+one_day_ago = now - timedelta(days=1)
+timestamp_one_day_ago = one_day_ago.timestamp()
+
+
 DATA: AntaUnitTestDataDict = {
     (VerifyInterfaceUtilization, "success"): {
         "eos_data": [
@@ -3431,4 +3439,519 @@ DATA: AntaUnitTestDataDict = {
             ],
         },
     },
+    (VerifyPhysicalInterfacesCounterDetails, "success"): {
+        "eos_data": [
+            {
+                "interfaces": {
+                    "Ethernet2": {
+                        "name": "Ethernet2",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "outTotalPkts": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                    "Ethernet4": {
+                        "name": "Ethernet4",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                    "Ethernet1": {
+                        "name": "Ethernet1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                    "Ethernet3": {
+                        "name": "Ethernet3",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "outTotalPkts": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                }
+            },
+        ],
+        "inputs": {
+            "interfaces": ["Ethernet1", "Ethernet2", "Ethernet4"],
+            "ignored_interfaces": ["Ethernet3"],
+            "counters_threshold": 0,
+            "link_status_changes_threshold": 100,
+        },
+        "expected": {
+            "result": AntaTestStatus.SUCCESS,
+        },
+    },
+    (VerifyPhysicalInterfacesCounterDetails, "failure-multiple-issues"): {
+        "eos_data": [
+            {
+                "interfaces": {
+                    "Ethernet2": {
+                        "name": "Ethernet2",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 10,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 12,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                    "Management0": {
+                        "name": "Management0",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "OOB_MANAGEMENT",
+                        "interfaceCounters": {
+                            "inDiscards": 20,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 1,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 10,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 10},
+                        },
+                    },
+                    "Ethernet10": {
+                        "name": "Ethernet10",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "hardware": "ethernet",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 10,
+                            "linkStatusChanges": 12,
+                            "totalInErrors": 10,
+                            "inputErrorsDetail": {"runtFrames": 10, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 20, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                }
+            }
+        ],
+        "inputs": {"interfaces": ["Management0", "Ethernet10"], "link_status_changes_threshold": 2},
+        "expected": {
+            "result": AntaTestStatus.FAILURE,
+            "messages": [
+                "Interface: Management0 Description: OOB_MANAGEMENT - Input discards above threshold - Expected: 0 Actual: 20",
+                "Interface: Management0 Description: OOB_MANAGEMENT - Output errors above threshold - Expected: 0 Actual: 10",
+                "Interface: Ethernet10 Uptime: 1 day - Link status changes count above threshold - Expected: < 2 Actual: 12",
+                "Interface: Ethernet10 Uptime: 1 day - Output discards above threshold - Expected: 0 Actual: 10",
+                "Interface: Ethernet10 Uptime: 1 day - Input errors above threshold - Expected: 0 Actual: 10",
+                "Interface: Ethernet10 Uptime: 1 day - Runt frames above threshold - Expected: 0 Actual: 10",
+                "Interface: Ethernet10 Uptime: 1 day - Late collisions above threshold - Expected: 0 Actual: 20",
+            ],
+        },
+    },
+    (VerifyPhysicalInterfacesCounterDetails, "failure-input-error"): {
+        "eos_data": [
+            {
+                "interfaces": {
+                    "Management1": {
+                        "name": "Management1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "outTotalPkts": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 30, "giantFrames": 0, "fcsErrors": 10, "alignmentErrors": 55, "symbolErrors": 20, "rxPause": 30},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 10, "lateCollisions": 10, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                    "Ethernet4": {
+                        "name": "Ethernet4",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 30, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 30, "deferredTransmissions": 0, "txPause": 30},
+                        },
+                    },
+                    "Loopback0": {
+                        "name": "Loopback0",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 30, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 30, "deferredTransmissions": 0, "txPause": 30},
+                        },
+                    },
+                }
+            },
+        ],
+        "inputs": {"ignored_interfaces": ["Ethernet4"], "counters_threshold": 10, "link_status_changes_threshold": 10},
+        "expected": {
+            "result": AntaTestStatus.FAILURE,
+            "messages": [
+                "Interface: Management1 Uptime: 1 day - Runt frames above threshold - Expected: < 10 Actual: 30",
+                "Interface: Management1 Uptime: 1 day - Alignment errors above threshold - Expected: < 10 Actual: 55",
+                "Interface: Management1 Uptime: 1 day - Symbol errors above threshold - Expected: < 10 Actual: 20",
+            ],
+        },
+    },
+    (VerifyPhysicalInterfacesCounterDetails, "failure-output-error"): {
+        "eos_data": [
+            {
+                "interfaces": {
+                    "Management1": {
+                        "name": "Management1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "outTotalPkts": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 10, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 10, "symbolErrors": 20, "rxPause": 30},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                    "Ethernet4": {
+                        "name": "Ethernet4",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 20, "lateCollisions": 30, "deferredTransmissions": 0, "txPause": 30},
+                        },
+                    },
+                }
+            },
+        ],
+        "inputs": {"counters_threshold": 0, "link_status_changes_threshold": 20},
+        "expected": {
+            "result": AntaTestStatus.FAILURE,
+            "messages": [
+                "Interface: Management1 Uptime: 1 day - Runt frames above threshold - Expected: 0 Actual: 10",
+                "Interface: Management1 Uptime: 1 day - Alignment errors above threshold - Expected: 0 Actual: 10",
+                "Interface: Management1 Uptime: 1 day - Symbol errors above threshold - Expected: 0 Actual: 20",
+                "Interface: Ethernet4 Uptime: 1 day - Collisions above threshold - Expected: 0 Actual: 20",
+                "Interface: Ethernet4 Uptime: 1 day - Late collisions above threshold - Expected: 0 Actual: 30",
+            ],
+        },
+    },
+    (VerifyPhysicalInterfacesCounterDetails, "failure-total-int-out-error"): {
+        "eos_data": [
+            {
+                "interfaces": {
+                    "Management1": {
+                        "name": "Management1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "outTotalPkts": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 10,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 30},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 30},
+                        },
+                    },
+                    "Ethernet4": {
+                        "name": "Ethernet4",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 20},
+                            "totalOutErrors": 30,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 30},
+                        },
+                    },
+                }
+            },
+        ],
+        "inputs": {"counters_threshold": 0, "link_status_changes_threshold": 20},
+        "expected": {
+            "result": AntaTestStatus.FAILURE,
+            "messages": [
+                "Interface: Management1 Uptime: 1 day - Input errors above threshold - Expected: 0 Actual: 10",
+                "Interface: Ethernet4 Uptime: 1 day - Output errors above threshold - Expected: 0 Actual: 30",
+            ],
+        },
+    },
+    (VerifyPhysicalInterfacesCounterDetails, "failure-int-out-packet-discard"): {
+        "eos_data": [
+            {
+                "interfaces": {
+                    "Management1": {
+                        "name": "Management1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 30,
+                            "outTotalPkts": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 10,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 10, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 30},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 30},
+                        },
+                    },
+                    "Ethernet4": {
+                        "name": "Ethernet4",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 30,
+                            "outDiscards": 10,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 10, "symbolErrors": 0, "rxPause": 20},
+                            "totalOutErrors": 10,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 10, "deferredTransmissions": 0, "txPause": 30},
+                        },
+                    },
+                }
+            },
+        ],
+        "inputs": {"counters_threshold": 10, "link_status_changes_threshold": 20},
+        "expected": {
+            "result": AntaTestStatus.FAILURE,
+            "messages": [
+                "Interface: Management1 Uptime: 1 day - Output discards above threshold - Expected: < 10 Actual: 30",
+                "Interface: Ethernet4 Uptime: 1 day - Input discards above threshold - Expected: < 10 Actual: 30",
+            ],
+        },
+    },
+    (VerifyPhysicalInterfacesCounterDetails, "failure-link-status-changes"): {
+        "eos_data": [
+            {
+                "interfaces": {
+                    "Management1/1": {
+                        "name": "Management1/1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 30,
+                            "outTotalPkts": 0,
+                            "linkStatusChanges": 45,
+                            "totalInErrors": 10,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 10, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 30},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 30},
+                        },
+                    },
+                    "Ethernet4/1": {
+                        "name": "Ethernet4/1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "down",
+                        "interfaceStatus": "notconnected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 30,
+                            "outDiscards": 10,
+                            "linkStatusChanges": 40,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 10, "symbolErrors": 0, "rxPause": 20},
+                            "totalOutErrors": 10,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 10, "deferredTransmissions": 0, "txPause": 30},
+                        },
+                    },
+                    "Management0": {
+                        "name": "Management0",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "down",
+                        "interfaceStatus": "notconnected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                    },
+                }
+            },
+        ],
+        "inputs": {"ignored_interfaces": ["Management1/1"], "counters_threshold": 40, "link_status_changes_threshold": 20},
+        "expected": {
+            "result": AntaTestStatus.FAILURE,
+            "messages": ["Interface: Ethernet4/1 Downtime: 1 day - Link status changes count above threshold - Expected: < 20 Actual: 40"],
+        },
+    },
+    (VerifyPhysicalInterfacesCounterDetails, "failure-specific-interface-not-found"): {
+        "eos_data": [
+            {
+                "interfaces": {
+                    "Ethernet2/1": {
+                        "name": "Ethernet2/1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "outTotalPkts": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                    "Ethernet4/2/1": {
+                        "name": "Ethernet4/2/1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                    "Ethernet1/1/1": {
+                        "name": "Ethernet1/1/1",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                    "Ethernet3": {
+                        "name": "Ethernet3",
+                        "forwardingModel": "bridged",
+                        "lineProtocolStatus": "up",
+                        "interfaceStatus": "connected",
+                        "description": "",
+                        "lastStatusChangeTimestamp": timestamp_one_day_ago,
+                        "interfaceCounters": {
+                            "inDiscards": 0,
+                            "outDiscards": 0,
+                            "outTotalPkts": 0,
+                            "linkStatusChanges": 2,
+                            "totalInErrors": 0,
+                            "inputErrorsDetail": {"runtFrames": 0, "giantFrames": 0, "fcsErrors": 0, "alignmentErrors": 0, "symbolErrors": 0, "rxPause": 0},
+                            "totalOutErrors": 0,
+                            "outputErrorsDetail": {"collisions": 0, "lateCollisions": 0, "deferredTransmissions": 0, "txPause": 0},
+                        },
+                    },
+                }
+            },
+        ],
+        "inputs": {"interfaces": ["Ethernet12/1/1", "Ethernet13/2", "Ethernet4/2/1"], "counters_threshold": 0, "link_status_changes_threshold": 100},
+        "expected": {"result": AntaTestStatus.FAILURE, "messages": ["Interface: Ethernet12/1/1 - Not found", "Interface: Ethernet13/2 - Not found"]},
+    },
 }
diff --git a/tests/units/input_models/test_interfaces.py b/tests/units/input_models/test_interfaces.py
index c4bb8eff..035aee13 100644
--- a/tests/units/input_models/test_interfaces.py
+++ b/tests/units/input_models/test_interfaces.py
@@ -12,10 +12,16 @@ import pytest
 from pydantic import ValidationError
 
 from anta.input_models.interfaces import InterfaceState
-from anta.tests.interfaces import VerifyInterfaceIPv4, VerifyInterfacesSpeed, VerifyInterfacesStatus, VerifyLACPInterfacesStatus
+from anta.tests.interfaces import (
+    VerifyInterfaceIPv4,
+    VerifyInterfacesSpeed,
+    VerifyInterfacesStatus,
+    VerifyLACPInterfacesStatus,
+    VerifyPhysicalInterfacesCounterDetails,
+)
 
 if TYPE_CHECKING:
-    from anta.custom_types import Interface, PortChannelInterface
+    from anta.custom_types import EthernetInterface, Interface, ManagementInterface, PortChannelInterface, PositiveInteger
 
 
 class TestInterfaceState:
@@ -133,3 +139,43 @@ class TestVerifyInterfacesSpeedInput:
         """Test VerifyInterfacesSpeed.Input invalid inputs."""
         with pytest.raises(ValidationError):
             VerifyInterfacesSpeed.Input(interfaces=interfaces)
+
+
+class TestVerifyPhysicalInterfacesCounterDetailsInput:
+    """Test anta.tests.interfaces.VerifyPhysicalInterfacesCounterDetails.Input."""
+
+    @pytest.mark.parametrize(
+        ("interfaces", "ignored_interfaces", "link_status_changes_threshold"),
+        [
+            pytest.param(["Ethernet1"], ["Management1/1"], 10, id="valid-interfaces-is-given"),
+        ],
+    )
+    def test_valid(
+        self,
+        interfaces: list[EthernetInterface | ManagementInterface],
+        ignored_interfaces: list[EthernetInterface | ManagementInterface],
+        link_status_changes_threshold: PositiveInteger,
+    ) -> None:
+        """Test VerifyPhysicalInterfacesCounterDetails.Input valid inputs."""
+        VerifyPhysicalInterfacesCounterDetails.Input(
+            interfaces=interfaces, ignored_interfaces=ignored_interfaces, link_status_changes_threshold=link_status_changes_threshold
+        )
+
+    @pytest.mark.parametrize(
+        ("interfaces", "ignored_interfaces", "link_status_changes_threshold"),
+        [
+            pytest.param(["Ethernet1"], ["Ethernet1"], 10, id="invalid-interfaces"),
+            pytest.param(["et1"], ["Ethernet1"], 10, id="invalid-interfaces"),
+        ],
+    )
+    def test_invalid(
+        self,
+        interfaces: list[EthernetInterface | ManagementInterface],
+        ignored_interfaces: list[EthernetInterface | ManagementInterface],
+        link_status_changes_threshold: PositiveInteger,
+    ) -> None:
+        """Test VerifyPhysicalInterfacesCounterDetails.Input invalid inputs."""
+        with pytest.raises(ValidationError):
+            VerifyPhysicalInterfacesCounterDetails.Input(
+                interfaces=interfaces, ignored_interfaces=ignored_interfaces, link_status_changes_threshold=link_status_changes_threshold
+            )
diff --git a/tests/units/test_tools.py b/tests/units/test_tools.py
index d2029936..83442d14 100644
--- a/tests/units/test_tools.py
+++ b/tests/units/test_tools.py
@@ -7,11 +7,12 @@ from __future__ import annotations
 
 from contextlib import AbstractContextManager
 from contextlib import nullcontext as does_not_raise
+from datetime import datetime, timedelta, timezone
 from typing import Any
 
 import pytest
 
-from anta.tools import convert_categories, custom_division, format_data, get_dict_superset, get_failed_logs, get_item, get_value, is_interface_ignored
+from anta.tools import convert_categories, custom_division, format_data, get_dict_superset, get_failed_logs, get_item, get_value, is_interface_ignored, time_ago
 
 TEST_GET_FAILED_LOGS_DATA = [
     {"id": 1, "name": "Alice", "age": 30, "email": "alice@example.com"},
@@ -611,3 +612,28 @@ def test_is_interface_ignored(input_params: dict[str, Any], expected_output: boo
     interface_value = input_params["interface"]
     ignored_interfaces_value = input_params["ignored_interfaces"]
     assert is_interface_ignored(interface_value, ignored_interfaces_value) == expected_output
+
+
+@pytest.mark.parametrize(
+    ("time_delta", "expected_output"),
+    [
+        pytest.param(timedelta(seconds=30), "less than a minute", id="less_than_a_minute"),
+        pytest.param(timedelta(minutes=1), "1 minute", id="one_minute"),
+        pytest.param(timedelta(minutes=1, seconds=10), "1 minute", id="one_minute_plus_seconds"),
+        pytest.param(timedelta(minutes=15), "15 minutes", id="multiple_minutes"),
+        pytest.param(timedelta(hours=1), "1 hour", id="one_hour"),
+        pytest.param(timedelta(hours=1, minutes=10), "1 hour and 10 minutes", id="one_hour_and_minutes"),
+        pytest.param(timedelta(hours=3), "3 hours", id="multiple_hours"),
+        pytest.param(timedelta(hours=2, minutes=45), "2 hours and 45 minutes", id="multiple_hours_and_minutes"),
+        pytest.param(timedelta(days=1), "1 day", id="one_day"),
+        pytest.param(timedelta(days=1, hours=5), "1 day", id="one_day_plus_hours"),
+        pytest.param(timedelta(days=5), "5 days", id="multiple_days"),
+        pytest.param(timedelta(seconds=0), "less than a minute", id="exactly_now"),
+    ],
+)
+def test_time_ago(time_delta: timedelta, expected_output: str) -> None:
+    """Tests the time_ago function with various time deltas."""
+    now = datetime.now(timezone.utc)
+    test_timestamp = (now - time_delta).timestamp()
+
+    assert time_ago(test_timestamp) == expected_output