Repository: pytest-dev/pytest-html (language: python)

Issue:
Changing dir in test makes pytest-html fail
I use a fixture like the following:

```python
@pytest.fixture()
def tmp_working_dir(tmp_path, monkeypatch):
    """Change working directory before a test and change it back when the test is finished."""
    monkeypatch.chdir(tmp_path)
    yield tmp_path
```

It was working well  with `pytest-html<4` but with `pytest-html>=4` I get the following error:
```python
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/_pytest/main.py", line 271, in wrap_session
INTERNALERROR>     session.exitstatus = doit(config, session) or 0
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/_pytest/main.py", line 325, in _main
INTERNALERROR>     config.hook.pytest_runtestloop(session=session)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_hooks.py", line 493, in __call__
INTERNALERROR>     return self._hookexec(self.name, self._hookimpls, kwargs, firstresult)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_manager.py", line 115, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_callers.py", line 130, in _multicall
INTERNALERROR>     teardown[0].send(outcome)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_result.py", line 114, in get_result
INTERNALERROR>     raise exc.with_traceback(exc.__traceback__)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_callers.py", line 77, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/_pytest/main.py", line 350, in pytest_runtestloop
INTERNALERROR>     item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_hooks.py", line 493, in __call__
INTERNALERROR>     return self._hookexec(self.name, self._hookimpls, kwargs, firstresult)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_manager.py", line 115, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_callers.py", line 152, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_result.py", line 114, in get_result
INTERNALERROR>     raise exc.with_traceback(exc.__traceback__)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_callers.py", line 77, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/_pytest/runner.py", line 114, in pytest_runtest_protocol
INTERNALERROR>     runtestprotocol(item, nextitem=nextitem)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/_pytest/runner.py", line 133, in runtestprotocol
INTERNALERROR>     reports.append(call_and_report(item, "call", log))
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/_pytest/runner.py", line 226, in call_and_report
INTERNALERROR>     hook.pytest_runtest_logreport(report=report)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_hooks.py", line 493, in __call__
INTERNALERROR>     return self._hookexec(self.name, self._hookimpls, kwargs, firstresult)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_manager.py", line 115, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_callers.py", line 113, in _multicall
INTERNALERROR>     raise exception.with_traceback(exception.__traceback__)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pluggy/_callers.py", line 77, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pytest_html/basereport.py", line 251, in pytest_runtest_logreport
INTERNALERROR>     self._generate_report()
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pytest_html/selfcontained_report.py", line 39, in _generate_report
INTERNALERROR>     super()._generate_report(self_contained=True)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pytest_html/basereport.py", line 65, in _generate_report
INTERNALERROR>     self._write_report(rendered_report)
INTERNALERROR>   File "/tmp/my-repo/.tox/py38/lib/python3.8/site-packages/pytest_html/basereport.py", line 134, in _write_report
INTERNALERROR>     with self._report_path.open("w", encoding="utf-8") as f:
INTERNALERROR>   File "/usr/local/lib/python3.8/pathlib.py", line 1222, in open
INTERNALERROR>     return io.open(self, mode, buffering, encoding, errors, newline,
INTERNALERROR>   File "/usr/local/lib/python3.8/pathlib.py", line 1078, in _opener
INTERNALERROR>     return self._accessor.open(self, flags, mode)
INTERNALERROR> FileNotFoundError: [Errno 2] No such file or directory: 'reports/pytest-py38.html'
```

I use the following command to call pytest in a tox job:
```shell
    pytest \
        --basetemp={envtmpdir} \
        --cov={[base]name} \
        --cov-branch \
        --cov-fail-under=64 \
        --no-cov-on-fail \
        --cov-report term-missing \
        --cov-report html:reports/coverage-{envname} \
        --cov-report xml:reports/coverage-{envname}.xml \
        --html reports/pytest-{envname}.html \
        --junit-xml=reports/pytest-{envname}.xml \
        --self-contained-html \
        {posargs}
```


Discussion / hints:
Oh, that's interesting!

Can you provide a complete minimal example?
Hi @BeyondEvil 
Here is a simple project that shows the issue: [pytest-html-issue-732.zip](https://github.com/pytest-dev/pytest-html/files/12574092/pytest-html-issue-732.zip)
If you run the command `tox run -e py310`, you will see the failure.

Please note the following behaviors:
* if you set `pytest-html<4` in `setup.py` the tests pass.
* if you change the order of the tests in `tests/test_example.py`, `pytest-html` will fail after the first test using the `monkeypatch.chdir` passes, so it seems that `pytest-html` is executed after the test passes but before the fixture teardowns are executed, while I think it should be executed after all the fixture teardowns.
> Hi @BeyondEvil Here is a simple project that shows the issue: [pytest-html-issue-732.zip](https://github.com/pytest-dev/pytest-html/files/12574092/pytest-html-issue-732.zip) If you run the command `tox run -e py310`, you will see the failure.
> 
> Please note the following behaviors:
> 
> * if you set `pytest-html<4` in `setup.py` the tests pass.
> * if you change the order of the tests in `tests/test_example.py`, `pytest-html` will fail after the first test using the `monkeypatch.chdir` passes, so it seems that `pytest-html` is executed after the test passes but before the fixture teardowns are executed, while I think it should be executed after all the fixture teardowns.

Thanks for the example!

As for your second bullet point. A pytest test (item) has three states: setup, call, teardown. Since we create an entry in the result table depending on where a tests fails - we can't wait until teardown has happened.

Regardless, I'm 99% sure that if we fix the root issue here (which likely has to do with relative vs. absolute paths) that won't be an issue.
Getting an error when running your example:

```
☸ deployment (ci) in ~/Downloads/pytest-html-issue-732 via 🐍 v3.9.10 (.venv) 
❯ tox run -e py39       
.pkg: _optional_hooks> python /Users/jimbrannlund/Downloads/pytest-html-issue-732/.venv/lib/python3.9/site-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: get_requires_for_build_sdist> python /Users/jimbrannlund/Downloads/pytest-html-issue-732/.venv/lib/python3.9/site-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: get_requires_for_build_wheel> python /Users/jimbrannlund/Downloads/pytest-html-issue-732/.venv/lib/python3.9/site-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: prepare_metadata_for_build_wheel> python /Users/jimbrannlund/Downloads/pytest-html-issue-732/.venv/lib/python3.9/site-packages/pyproject_api/_backend.py True setuptools.build_meta
py39: packaging backend failed (code=1), with LookupError: setuptools-scm was unable to detect version for /Users/jimbrannlund/Downloads/pytest-html-issue-732.
```
Also, I'd prefer to be able to repro this without `tox`, which I can't.
> As for your second bullet point. A pytest test (item) has three states: setup, call, teardown. Since we create an entry in the result table depending on where a tests fails - we can't wait until teardown has happened.
> 
> Regardless, I'm 99% sure that if we fix the root issue here (which likely has to do with relative vs. absolute paths) that won't be an issue.

Indeed it should work with absolute paths and it's simpler.

> Getting an error when running your example:
> 
> ```
> ☸ deployment (ci) in ~/Downloads/pytest-html-issue-732 via 🐍 v3.9.10 (.venv) 
> ❯ tox run -e py39       
> .pkg: _optional_hooks> python /Users/jimbrannlund/Downloads/pytest-html-issue-732/.venv/lib/python3.9/site-packages/pyproject_api/_backend.py True setuptools.build_meta
> .pkg: get_requires_for_build_sdist> python /Users/jimbrannlund/Downloads/pytest-html-issue-732/.venv/lib/python3.9/site-packages/pyproject_api/_backend.py True setuptools.build_meta
> .pkg: get_requires_for_build_wheel> python /Users/jimbrannlund/Downloads/pytest-html-issue-732/.venv/lib/python3.9/site-packages/pyproject_api/_backend.py True setuptools.build_meta
> .pkg: prepare_metadata_for_build_wheel> python /Users/jimbrannlund/Downloads/pytest-html-issue-732/.venv/lib/python3.9/site-packages/pyproject_api/_backend.py True setuptools.build_meta
> py39: packaging backend failed (code=1), with LookupError: setuptools-scm was unable to detect version for /Users/jimbrannlund/Downloads/pytest-html-issue-732.
> ```

Ah yes I guess you have to initialize a git repo and commit everything to make `setuptools-scm` work, I forgot about this...

> Also, I'd prefer to be able to repro this without `tox`, which I can't.

To run it without tox you can just create a virtualenv and do:
```bash
pip install ".[test]"
pytest --html reports/pytest-py39.html
```

Here is a new version to fix the version stuff, so it should be simpler to install it: [pytest-html-issue-732.zip](https://github.com/pytest-dev/pytest-html/files/12574837/pytest-html-issue-732.zip)


Fix patch:
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 9b2c5f2a..bc3d800b 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -6,6 +6,13 @@ Versions follow `Semantic Versioning`_ (``<major>.<minor>.<patch>``).
 Version History
 ---------------
 
+4.0.2 (2023-09-12)
+~~~~~~~~~~~~~~~~~~
+
+* Use absolute path to the report file.
+
+  * Thanks to `@adrien-berchet <https://github.com/adrien-berchet>`_ for reporting and for the PR.
+
 4.0.1 (2023-09-10)
 ~~~~~~~~~~~~~~~~~~
 


Test patch:
diff --git a/src/pytest_html/basereport.py b/src/pytest_html/basereport.py
index 1cccc939..b160e910 100644
--- a/src/pytest_html/basereport.py
+++ b/src/pytest_html/basereport.py
@@ -18,7 +18,9 @@
 
 class BaseReport:
     def __init__(self, report_path, config, report_data, template, css):
-        self._report_path = Path(os.path.expandvars(report_path)).expanduser()
+        self._report_path = (
+            Path.cwd() / Path(os.path.expandvars(report_path)).expanduser()
+        )
         self._report_path.parent.mkdir(parents=True, exist_ok=True)
         self._config = config
         self._template = template
@@ -186,7 +188,7 @@ def pytest_sessionfinish(self, session):
     def pytest_terminal_summary(self, terminalreporter):
         terminalreporter.write_sep(
             "-",
-            f"Generated html report: file://{self._report_path.resolve().as_posix()}",
+            f"Generated html report: {self._report_path.as_uri()}",
         )
 
     @pytest.hookimpl(trylast=True)
diff --git a/testing/test_unit.py b/testing/test_unit.py
index be8ef387..f82825ce 100644
--- a/testing/test_unit.py
+++ b/testing/test_unit.py
@@ -1,6 +1,7 @@
 import importlib.resources
 import os
 import sys
+from pathlib import Path
 
 import pkg_resources
 import pytest
@@ -70,6 +71,27 @@ def pytest_html_results_summary(prefix, summary, postfix, session):
     result.assert_outcomes(passed=1)
 
 
+def test_chdir(pytester):
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture
+        def changing_dir(tmp_path, monkeypatch):
+            monkeypatch.chdir(tmp_path)
+
+        def test_function(changing_dir):
+            pass
+    """
+    )
+    report_path = Path("reports") / "report.html"
+    page = pytester.runpytest("--html", str(report_path))
+    assert page.ret == 0
+    assert (
+        f"Generated html report: {(pytester.path / report_path).as_uri()}"
+    ) in page.outlines[-2]
+
+
 @pytest.fixture
 def css_file_path(pytester):
     css_one = """