Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
name: Documentation

# The keyword documentation is generated from the library itself and published to the
# gh-pages branch, one directory per released version plus /dev for the current main.
#
# It is not committed to the repository. A rendered libdoc page carries the generation
# time, the absolute path of the machine that produced it and the Robot Framework and
# Python versions used, so a committed copy cannot be compared against a fresh one
# without normalising all of that away - which was tried, was fragile, and failed three
# release attempts before being removed. Generating on publish means there is nothing
# that can drift.

on:
push:
branches: [main]
tags: ["v*"]
workflow_dispatch:

permissions:
contents: read

concurrency:
# Publishing rewrites a shared branch, so two runs must not do it at once.
group: docs
cancel-in-progress: false

jobs:
publish:
runs-on: ubuntu-latest
permissions:
# Only this job writes, and only to the gh-pages branch.
contents: write
steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v7
with:
python-version: "3.12"

- name: Install Poetry
run: pipx install poetry

- name: Install
run: poetry install

- name: Work out what is being published
id: target
run: |
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
echo "path=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
echo "release=true" >> "$GITHUB_OUTPUT"
else
echo "path=dev" >> "$GITHUB_OUTPUT"
echo "release=false" >> "$GITHUB_OUTPUT"
fi

- name: Generate the keyword documentation
run: poetry run python -m robot.libdoc MitmLibrary MitmLibraryKeywords.html

- name: Check out the published site
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if git ls-remote --exit-code --heads origin gh-pages; then
git fetch origin gh-pages --depth 1
git worktree add site origin/gh-pages
else
# First publish: start the branch with no history of its own.
git worktree add --detach site
git -C site checkout --orphan gh-pages
git -C site rm -rf . --quiet || true
fi

- name: Add this version to the site
run: |
set -eu
path="${{ steps.target.outputs.path }}"
mkdir -p "site/${path}"
cp MitmLibraryKeywords.html "site/${path}/MitmLibraryKeywords.html"

if [ "${{ steps.target.outputs.release }}" = "true" ]; then
# The bare path is what pyproject.toml, the README and the metadata of every
# already published release point at, so it stays as the newest release.
cp MitmLibraryKeywords.html site/MitmLibraryKeywords.html
mkdir -p site/latest
cp MitmLibraryKeywords.html site/latest/MitmLibraryKeywords.html
fi

# GitHub Pages otherwise runs the site through Jekyll, which drops directories
# whose names begin with an underscore and needs no help here regardless.
touch site/.nojekyll

if [ "${{ steps.target.outputs.release }}" = "true" ]; then
poetry run python tools/build_docs_index.py \
site/versions.json site/index.html --add "$path" --release
else
poetry run python tools/build_docs_index.py \
site/versions.json site/index.html --add "$path"
fi

- name: Publish
run: |
cd site
git add -A
if git diff --cached --quiet; then
echo "The published documentation is already up to date."
exit 0
fi
git commit -m "docs: publish ${{ steps.target.outputs.path }} from ${GITHUB_SHA}"
git push origin HEAD:gh-pages
45 changes: 40 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ jobs:
runs-on: ubuntu-latest
environment: pypi
permissions:
# Declaring any permission replaces the whole default set, so contents: read has
# to be named too. Without it the token cannot clone a private repository and
# checkout fails with "repository not found", which reads like a wrong URL rather
# than a missing scope.
contents: read
# Declaring any permission replaces the whole default set, so contents has to be
# named too. Without it the token cannot clone a private repository and checkout
# fails with "repository not found", which reads like a wrong URL rather than a
# missing scope. It is write rather than read because this job also creates the
# GitHub release for the tag.
contents: write
# Trusted publishing, so no token is stored in the repository.
id-token: write
steps:
Expand All @@ -38,4 +39,38 @@ jobs:
- name: Build
run: poetry build

- name: Take the release notes from the changelog
# A release with no notes is a release nobody can read, and the changelog already
# says what changed. This lifts the section for this version out of it.
run: |
version="${GITHUB_REF_NAME#v}"
python - "$version" <<'PY' > release-notes.md
import re, sys
from pathlib import Path

version = sys.argv[1]
changelog = Path("CHANGELOG.md").read_text(encoding="utf-8")
# Everything between this version's heading and the next version heading.
match = re.search(
rf"^## \[{re.escape(version)}\][^\n]*\n(.*?)(?=^## \[|\Z)",
changelog,
re.MULTILINE | re.DOTALL,
)
if match is None:
raise SystemExit(f"CHANGELOG.md has no section for {version}.")
print(match.group(1).strip())
PY

- uses: pypa/gh-action-pypi-publish@release/v1

- name: Create the GitHub release
env:
GH_TOKEN: ${{ github.token }}
# After publishing, so a release is never announced for a version that failed to
# reach PyPI.
run: |
gh release create "$GITHUB_REF_NAME" \
--title "${GITHUB_REF_NAME#v}" \
--notes-file release-notes.md \
--verify-tag \
dist/*
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ promise in the README.

## [Unreleased]

Nothing yet.

## [1.0.0] - 2026-08-18

1.0.0 reworks the keyword surface once, deliberately, and then freezes it: from this
release onwards keyword names, argument names and their order will not change within 1.x.
See "API stability" in the README.

### Changed - breaking

Every kind of manipulation used to have its own list, its own remove keyword and its own
Expand Down Expand Up @@ -125,4 +133,5 @@ Two behaviour changes come with it:
module that reaches into mitmproxy's internals. This is internal, but it does move
`MitmLibrary.STARTUP_TIMEOUT` and `MitmLibrary.SHUTDOWN_TIMEOUT` to that module.

[Unreleased]: https://github.com/MobyNL/robotframework-mitmlibrary/compare/v0.3.0...HEAD
[Unreleased]: https://github.com/MobyNL/robotframework-mitmlibrary/compare/v1.0.0...HEAD
[1.0.0]: https://github.com/MobyNL/robotframework-mitmlibrary/compare/v0.3.0...v1.0.0
68 changes: 57 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@ If you need help, have suggestions or want to discuss anything, feel free to con

## Features

- Interact with MITM proxy using Robot Framework keywords.
- Manipulate network traffic for testing purposes.
- Easily simulate different network conditions and responses.
- Integrate MITM proxy capabilities into your existing Robot Framework tests.
- **Change what comes back.** Replace a response, or only its status, headers or body,
for requests matching a url, a method, or a regular expression.
- **Change what goes out.** Add or remove request headers, replace a request body, or
send a request to a different url or host entirely.
- **Assert on what was sent.** Record the traffic that passed through and ask whether a
request was made, how often, and with what — the half of testing a proxy is usually not
used for.
- **Break things on purpose.** Block a request, drop a connection, hold a request until
the client gives up, or cut a response short while it still claims its full length.
- **Sit wherever the traffic is.** A forward proxy by default, or in front of a service,
or chained through the network's own proxy.

Every rule is addressed by an alias, matched the same way, and removed the same way.

## Installation

Expand All @@ -26,6 +35,8 @@ If you need help, have suggestions or want to discuss anything, feel free to con
pip install robotframework-mitmlibrary
```

Requires Python 3.12 or newer, which is mitmproxy's own floor.


## Usage

Expand Down Expand Up @@ -165,17 +176,52 @@ mitm. Follow the guide on the
[Mitm website](https://docs.mitmproxy.org/stable/concepts-certificates/)

## Documentation
For detailed information on the available keywords and usage examples, please refer to the [Keyword Documentation](https://mobynl.github.io/robotframework-mitmlibrary/MitmLibraryKeywords.html)

The [keyword documentation](https://mobynl.github.io/robotframework-mitmlibrary/MitmLibraryKeywords.html)
describes every keyword, its arguments and examples.

It is published per version, so you can read the documentation for the version you
actually have installed rather than for whatever is newest:

- [latest release](https://mobynl.github.io/robotframework-mitmlibrary/latest/MitmLibraryKeywords.html)
- [all versions](https://mobynl.github.io/robotframework-mitmlibrary/)
- [current main, unreleased](https://mobynl.github.io/robotframework-mitmlibrary/dev/MitmLibraryKeywords.html)

## API stability

The keyword surface is **not yet stable**. Keyword names, argument names and their order
may still change while the library is on 0.x, and the [CHANGELOG](CHANGELOG.md) records
those changes.
From 1.0.0 onwards the keyword surface is stable:

- Keyword names, argument names and their order will not change in a 1.x release.
- New arguments are only ever added at the end, with defaults, so existing calls keep
working whether they pass arguments positionally or by name.
- The rule model is part of that promise, not just the signatures: how patterns are
matched, the order in which several matching rules are applied, and what `times` means
will not change either.

Anything not listed above is internal and may change: module layout, class names, and
everything with a leading underscore. Import keywords through Robot Framework rather than
calling into the package directly, and none of that will reach you.

Breaking changes wait for 2.0 and are recorded in the [CHANGELOG](CHANGELOG.md).

### Migrating from 0.3.0

1.0.0 reworked the keywords once so that every kind of rule is addressed the same way.
The [CHANGELOG](CHANGELOG.md) has the full table; in short:

| Before | Now |
| --- | --- |
| `Add To Blocklist url` | `Block Requests alias url` |
| `Add Custom Response alias url overwrite_headers= overwrite_body=` | `Set Response alias url headers= body=` |
| `Add Custom Response Status Code` | `Set Response Status` |
| `Remove Url From Blocklist`, `Remove Custom Response`, `Remove Custom Status Code` | `Remove Rule alias` |
| `Clear All Proxy Items` | `Clear All Rules` |
| the four `Log ...` keywords | `Log Proxy Rules` |

1.0.0 fixes the surface: it reworks the keywords once, deliberately and with a documented
migration path, and from that release onwards names and argument order will not change
within 1.x.
Two behaviour changes come with it: a blocked request is answered with `403` rather than
having its connection dropped (`mode=RESET` restores the old behaviour), and when several
rules match one request all of them apply, in a defined order, instead of the last one
silently winning.

## Contributing
Contributions are welcome! If you encounter any issues, have suggestions for improvements, or would like to add new features, feel free to open an issue or submit a pull request.
Expand Down
387 changes: 0 additions & 387 deletions docs/MitmLibraryKeywords.html

This file was deleted.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "robotframework-mitmlibrary"
version = "0.3.0"
version = "1.0.0"
description = "A Robot Framework library for managing and manipulating HTTP requests and responses using a man-in-the-middle proxy approach. It allows blocking requests, customizing responses, adding delays, and more."
authors = ["Mark <markmoberts@gmail.com>"]
license = "MIT"
Expand All @@ -18,7 +18,7 @@ keywords = [
"maninthemiddle",
]
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 5 - Production/Stable",
"Framework :: Robot Framework :: Library",
"Intended Audience :: Developers",
"Topic :: Software Development :: Testing",
Expand Down
Loading
Loading