codecov-io: # [Codecov](https://codecov.io/gh/scanapi/scanapi/pull/126?src=pr&el=h1) Report
> Merging [#126](https://codecov.io/gh/scanapi/scanapi/pull/126?src=pr&el=desc) into [master](https://codecov.io/gh/scanapi/scanapi/commit/a5a9354a4039db30a93ca8a13022183566185cd9&el=desc) will **increase** coverage by `7.31%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/scanapi/scanapi/pull/126/graphs/tree.svg?width=650&height=150&src=pr&token=xgH1xCg4s4)](https://codecov.io/gh/scanapi/scanapi/pull/126?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #126      +/-   ##
==========================================
+ Coverage   84.02%   91.33%   +7.31%     
==========================================
  Files          11       11              
  Lines         388      381       -7     
==========================================
+ Hits          326      348      +22     
+ Misses         62       33      -29     
```


| [Impacted Files](https://codecov.io/gh/scanapi/scanapi/pull/126?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [scanapi/reporter.py](https://codecov.io/gh/scanapi/scanapi/pull/126/diff?src=pr&el=tree#diff-c2NhbmFwaS9yZXBvcnRlci5weQ==) | `100.00% <100.00%> (+72.50%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/scanapi/scanapi/pull/126?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/scanapi/scanapi/pull/126?src=pr&el=footer). Last update [a5a9354...5f0dccf](https://codecov.io/gh/scanapi/scanapi/pull/126?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a33243..de18241 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 - PATCH HTTP method - [#77](https://github.com/scanapi/scanapi/issues/77)
 - Ability to have API spec in multiples files - [#125](https://github.com/scanapi/scanapi/issues/125)
-- CLI `--config-path` option - [#128](https://github.com/scanapi/scanapi/issues/#128)
+- CLI `--config-path` option - [#128](https://github.com/scanapi/scanapi/issues/128)
+- CLI `--template-path` option - [#126](https://github.com/scanapi/scanapi/pull/126)
 
 ### Changed
 - Make markdown report a bit better - [#96](https://github.com/scanapi/scanapi/issues/96)
diff --git a/README.md b/README.md
index 559ef56..48ccd73 100644
--- a/README.md
+++ b/README.md
@@ -12,19 +12,21 @@ A library for **your API** that provides:
 
 ## Contents
 
-* [Requirements](#requirements)
-* [How to install](#how-to-install)
-* [Basic Usage](#basic-usage)
-* [Documentation](#documentation)
-  + [ScanAPI CLI](#scanapi-cli)
-  + [API Specification Keys](#api-specification-keys)
-  + [Environment Variables](#environment-variables)
-  + [Custom Variables](#custom-variables)
-  + [Python Code](#python-code)
-  + [Chaining Requests](#chaining-requests)
-  + [Configuration File](#configuration-file)
-  + [Hiding sensitive information](#hiding-sensitive-information)
-* [Contributing](#contributing)
+- [Contents](#contents)
+- [Requirements](#requirements)
+- [How to install](#how-to-install)
+- [Basic Usage](#basic-usage)
+- [Documentation](#documentation)
+  - [ScanAPI CLI](#scanapi-cli)
+  - [API Specification Keys](#api-specification-keys)
+  - [Environment Variables](#environment-variables)
+  - [Custom Variables](#custom-variables)
+  - [Python Code](#python-code)
+  - [Chaining Requests](#chaining-requests)
+  - [API specification in multiple files](#api-specification-in-multiple-files)
+  - [Configuration File](#configuration-file)
+  - [Hiding sensitive information](#hiding-sensitive-information)
+- [Contributing](#contributing)
 
 ## Requirements
 
@@ -81,7 +83,7 @@ Options:
   -o, --output-path PATH
   -c, --config-path PATH
   -r, --reporter [console|markdown|html]
-  -t, --template TEXT
+  -t, --template PATH
   --log-level [DEBUG|INFO|WARNING|ERROR|CRITICAL]
   --help                          Show this message and exit.
 ```
diff --git a/scanapi/__init__.py b/scanapi/__init__.py
index 1c9e6b3..77157dc 100644
--- a/scanapi/__init__.py
+++ b/scanapi/__init__.py
@@ -17,12 +17,12 @@ from scanapi.config_loader import load_config_file
 
 @click.command()
 @click.option("-s", "--spec-path", "spec_path", type=click.Path(exists=True))
-@click.option("-o", "--output-path", "output_path", type=click.Path(exists=True))
+@click.option("-o", "--output-path", "output_path", type=click.Path())
 @click.option("-c", "--config-path", "config_path", type=click.Path(exists=True))
 @click.option(
     "-r", "--reporter", "reporter", type=click.Choice(["console", "markdown", "html"])
 )
-@click.option("-t", "--template", "template")
+@click.option("-t", "--template", "template", type=click.Path(exists=True))
 @click.option(
     "--log-level",
     "log_level",
diff --git a/scanapi/reporter.py b/scanapi/reporter.py
index 131a898..63e3f82 100644
--- a/scanapi/reporter.py
+++ b/scanapi/reporter.py
@@ -2,7 +2,7 @@
 import logging
 import curlify
 
-from jinja2 import Environment, PackageLoader, select_autoescape
+from jinja2 import Environment, FileSystemLoader, PackageLoader, select_autoescape
 
 from scanapi.settings import settings
 
@@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
 
 
 class Reporter:
-    def __init__(self, output_path, reporter, template):
+    def __init__(self, output_path, reporter, template=None):
         self.output_path = output_path
         self.reporter = reporter
         self.template = template
@@ -18,11 +18,14 @@ class Reporter:
     def write(self, responses):
         logger.info("Writing documentation")
 
-        # self.hide_headers_info(responses) TODO: Move to RequestNode.run
-        env = Environment(loader=PackageLoader("scanapi", "templates"))
-        env.filters["curlify"] = curlify.to_curl
-        template = env.get_template(f"{self.reporter}.jinja")
-        content = template.render(responses=responses)
+        if self.template:
+            loader = FileSystemLoader(searchpath="./")
+            template_path = self.template
+        else:
+            loader = PackageLoader("scanapi", "templates")
+            template_path = f"{self.reporter}.jinja"
+
+        content = self._render_content(loader, template_path, responses)
 
         if self.reporter == "console":
             print(f"\n{content}")
@@ -30,7 +33,7 @@ class Reporter:
 
         if self.output_path is None:
             outputs = {"html": "scanapi-report.html", "markdown": "scanapi-report.md"}
-            self.output_path = outputs.get(self.reporter)
+            self.output_path = outputs.get(self.reporter) or "scanapi-report"
 
         with open(self.output_path, "w", newline="\n") as doc:
             doc.write(content)
@@ -38,26 +41,8 @@ class Reporter:
         logger.info("The documentation was generated successfully.")
         logger.info(f"It is available at {self.output_path}")
 
-    def hide_headers_info(self, responses):
-        hide_keys = self.hide_keys()
-
-        if not hide_keys:
-            return
-
-        [
-            self.hide_request_headers_info(response.request, hide_keys)
-            for response in responses
-        ]
-
-    def hide_request_headers_info(self, request, hide_keys):
-        request_headers = request.headers
-
-        for key in hide_keys:
-            if key in request_headers:
-                request_headers[key] = "<sensitive_information>"
-
-    def hide_keys(self):
-        if not settings.get("report") or not settings["report"].get("hide"):
-            return
-
-        return settings["report"]["hide"].get("headers")
+    def _render_content(self, loader, template_path, responses):
+        env = Environment(loader=loader)
+        env.filters["curlify"] = curlify.to_curl
+        chosen_template = env.get_template(template_path)
+        return chosen_template.render(responses=responses)

Add ability to use custom templates
related to #15 
diff --git a/tests/unit/test_reporter.py b/tests/unit/test_reporter.py
new file mode 100644
index 0000000..1848bd8
--- /dev/null
+++ b/tests/unit/test_reporter.py
@@ -0,0 +1,77 @@
+import pytest
+import jinja2
+from scanapi.reporter import Reporter
+
+fake_responses = [
+    {"status_code": 200, "request": {"method": "GET", "url": "http://test.com"}}
+]
+
+
+class TestConsoleReport:
+    @pytest.fixture
+    def mocked_print(self, mocker):
+        return mocker.patch("builtins.print")
+
+    def test_should_print(self, mocker, mocked_print):
+        console_reporter = Reporter(None, "console")
+        console_reporter.write(fake_responses)
+
+        expected_content = "\n".join(
+            (
+                "ScanAPI Report: Console",
+                "=======================",
+                "",
+                "GET http://test.com - 200",
+                "",
+            )
+        )
+
+        mocked_print.assert_called_once_with(f"\n{expected_content}")
+
+
+class TestReporterOtherThanConsole:
+    @pytest.fixture
+    def mocked__render_content(self, mocker):
+        return mocker.patch("scanapi.reporter.Reporter._render_content")
+
+    @pytest.fixture
+    def mocked_open(self, mocker):
+        mock = mocker.mock_open()
+        mocker.patch("builtins.open", mock)
+        return mock
+
+    @pytest.mark.parametrize("reporter_type", ["html", "markdown"])
+    def test_should_write_to_default_output(
+        self, reporter_type, mocker, mocked__render_content, mocked_open
+    ):
+        mocked__render_content.return_value = "ScanAPI Report"
+        reporter = Reporter(None, reporter_type)
+        reporter.write(fake_responses)
+
+        file_extension = {"html": "html", "markdown": "md"}[reporter_type]
+        mocked_open.assert_called_once_with(
+            f"scanapi-report.{file_extension}", "w", newline="\n"
+        )
+        mocked_open().write.assert_called_once_with("ScanAPI Report")
+
+    def test_should_write_to_custom_output(
+        self, mocker, mocked__render_content, mocked_open
+    ):
+        mocked__render_content.return_value = "ScanAPI Report"
+        reporter = Reporter("./custom/report-output.html", "html")
+        reporter.write(fake_responses)
+
+        mocked_open.assert_called_once_with(
+            "./custom/report-output.html", "w", newline="\n"
+        )
+        mocked_open().write.assert_called_once_with("ScanAPI Report")
+
+    def test_should_handle_custom_templates(
+        self, mocker, mocked__render_content, mocked_open
+    ):
+        mocked__render_content.return_value = "ScanAPI Report"
+        reporter = Reporter(None, None, "my-template.html")
+        reporter.write(fake_responses)
+
+        mocked_open.assert_called_once_with("scanapi-report", "w", newline="\n")
+        mocked_open().write.assert_called_once_with("ScanAPI Report")

pip install -e .[dev]
parse_log_pytest_v2
requirements.txt
pytest pytest-cov pytest-mock requests-mock
apt-get update apt-get install -y gcc
3.7
requirements/base.txt
pytest
backcall==0.2.0
black==23.3.0
certifi @ file:///croot/certifi_1671487769961/work/certifi
cfgv==3.3.1
charset-normalizer==3.4.1
click==8.1.8
coverage==7.2.7
curlify==2.2.1
decorator==5.1.1
distlib==0.3.9
exceptiongroup==1.2.2
filelock==3.12.2
identify==2.5.24
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
ipdb==0.13.13
ipython==7.34.0
jedi==0.19.2
Jinja2==3.1.6
MarkupSafe==2.1.5
matplotlib-inline==0.1.6
mypy-extensions==1.0.0
nodeenv==1.9.1
packaging==24.0
parso==0.8.4
pathspec==0.11.2
pexpect==4.9.0
pickleshare==0.7.5
platformdirs==4.0.0
pluggy==1.2.0
pre-commit==2.21.0
prompt_toolkit==3.0.48
ptyprocess==0.7.0
Pygments==2.17.2
pytest==7.4.4
pytest-cov==4.1.0
pytest-mock==3.11.1
PyYAML==6.0.1
requests==2.31.0
requests-mock==1.12.1
-e git+https://github.com/scanapi/scanapi.git@a5a9354a4039db30a93ca8a13022183566185cd9#egg=scanapi
tomli==2.0.1
traitlets==5.9.0
typed-ast==1.5.5
typing_extensions==4.7.1
urllib3==2.0.7
virtualenv==20.26.6
wcwidth==0.2.13
zipp==3.15.0

name: scanapi
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - backcall==0.2.0
      - black==23.3.0
      - cfgv==3.3.1
      - charset-normalizer==3.4.1
      - click==8.1.8
      - coverage==7.2.7
      - curlify==2.2.1
      - decorator==5.1.1
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.12.2
      - identify==2.5.24
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - ipdb==0.13.13
      - ipython==7.34.0
      - jedi==0.19.2
      - jinja2==3.1.6
      - markupsafe==2.1.5
      - matplotlib-inline==0.1.6
      - mypy-extensions==1.0.0
      - nodeenv==1.9.1
      - packaging==24.0
      - parso==0.8.4
      - pathspec==0.11.2
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - platformdirs==4.0.0
      - pluggy==1.2.0
      - pre-commit==2.21.0
      - prompt-toolkit==3.0.48
      - ptyprocess==0.7.0
      - pygments==2.17.2
      - pytest==7.4.4
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - pyyaml==6.0.1
      - requests==2.31.0
      - requests-mock==1.12.1
      - tomli==2.0.1
      - traitlets==5.9.0
      - typed-ast==1.5.5
      - typing-extensions==4.7.1
      - urllib3==2.0.7
      - virtualenv==20.26.6
      - wcwidth==0.2.13
      - zipp==3.15.0
prefix: /opt/conda/envs/scanapi

tests/unit/test_reporter.py::TestConsoleReport::test_should_print tests/unit/test_reporter.py::TestReporterOtherThanConsole::test_should_write_to_default_output[html] tests/unit/test_reporter.py::TestReporterOtherThanConsole::test_should_write_to_default_output[markdown] tests/unit/test_reporter.py::TestReporterOtherThanConsole::test_should_write_to_custom_output tests/unit/test_reporter.py::TestReporterOtherThanConsole::test_should_handle_custom_templates
scanapi/reporter.py scanapi/__init__.py README.md CHANGELOG.md
scanapi/reporter.py scanapi/__init__.py README.md CHANGELOG.md