Skip to content
Open
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
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,14 @@

# ScanAPI Examples

ScanAPI examples for different API's.
ScanAPI examples for different APIs.

| Example | Description |
|---------|-------------|
| [ci-smoke-tests](ci-smoke-tests/) | Run ScanAPI as a CI smoke test step in GitHub Actions after deployment |
| [demo-api](demo-api/) | Full-featured example using the ScanAPI Demo API |
| [github-api](github-api/) | Tests against the GitHub REST API |
| [httpbin-api](httpbin-api/) | Tests against httpbin.org |
| [httpbingo-api](httpbingo-api/) | Tests against httpbingo.org |
| [jsonplaceholder-api](jsonplaceholder-api/) | Tests against JSONPlaceholder |
| [pokeapi](pokeapi/) | Tests against the PokeAPI |
1 change: 1 addition & 0 deletions ci-smoke-tests/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export BASE_URL="https://jsonplaceholder.typicode.com"
112 changes: 112 additions & 0 deletions ci-smoke-tests/.github/workflows/smoke-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Reusable GitHub Actions workflow for running ScanAPI smoke tests
# after a deployment. Copy this file into your project's
# .github/workflows/ directory and adjust the inputs to match
# your setup.
#
# Usage:
# 1. As a standalone workflow (triggered manually or on push)
# 2. Called from another workflow after a deploy step
#
# For authenticated APIs, store your credentials as GitHub Secrets
# and pass them as environment variables (see the "with-auth" job).

name: Smoke Tests

on:
workflow_dispatch:
inputs:
base_url:
description: "Base URL of the deployed service"
required: true
default: "https://jsonplaceholder.typicode.com"
# Uncomment to run after every push:
# push:
# branches: [main]
# Uncomment to use as a reusable workflow called from deploy pipelines:
# workflow_call:
# inputs:
# base_url:
# required: true
# type: string

permissions:
contents: read

jobs:
smoke-test:
name: Run ScanAPI smoke tests
runs-on: ubuntu-latest
env:
BASE_URL: ${{ inputs.base_url || 'https://jsonplaceholder.typicode.com' }}
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install ScanAPI
run: pip install scanapi

- name: Run smoke tests
run: scanapi run scanapi.yaml -c scanapi.conf

- name: Upload HTML report
uses: actions/upload-artifact@v4
if: always()
with:
name: scanapi-smoke-test-report
path: scanapi-report.html

# -------------------------------------------------------
# Example: Authenticated API
# Uncomment this job if your API requires authentication.
# Store credentials in GitHub Secrets.
# -------------------------------------------------------
# smoke-test-with-auth:
# name: Run authenticated smoke tests
# runs-on: ubuntu-latest
# # Uncomment to run after a deploy job:
# # needs: [deploy]
# env:
# BASE_URL: ${{ inputs.base_url }}
# steps:
# - name: Checkout
# uses: actions/checkout@v4
#
# - name: Set up Python
# uses: actions/setup-python@v5
# with:
# python-version: "3.12"
#
# - name: Install ScanAPI
# run: pip install scanapi
#
# - name: Obtain access token
# run: |
# response=$(curl --silent --request POST \
# --url "https://${{ secrets.AUTH_DOMAIN }}/oauth/token" \
# --header "Content-Type: application/x-www-form-urlencoded" \
# --data-urlencode "client_id=${{ secrets.AUTH_CLIENT_ID }}" \
# --data-urlencode "client_secret=${{ secrets.AUTH_CLIENT_SECRET }}" \
# --data-urlencode "grant_type=client_credentials" \
# --data-urlencode "audience=${{ secrets.AUTH_AUDIENCE }}")
# token=$(echo "$response" | jq -r '.access_token')
# if [ "$token" = "null" ] || [ -z "$token" ]; then
# echo "::error::Failed to obtain access token"
# exit 1
# fi
# echo "ACCESS_TOKEN=$token" >> "$GITHUB_ENV"
# echo "::add-mask::$token"
#
# - name: Run smoke tests
# run: scanapi run scanapi.yaml -c scanapi.conf
#
# - name: Upload HTML report
# uses: actions/upload-artifact@v4
# if: always()
# with:
# name: scanapi-smoke-test-report
# path: scanapi-report.html
68 changes: 68 additions & 0 deletions ci-smoke-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# CI Smoke Tests

A ready-to-use example showing how to run ScanAPI as a **CI smoke test step** in GitHub Actions. Use it to verify your API is healthy after every deployment.

This example tests the public [JSONPlaceholder](https://jsonplaceholder.typicode.com) API and demonstrates:

- **Health checks** — verify the service is reachable
- **Read endpoint validation** — list, get, and 404 handling
- **Request chaining** — create a resource, extract its ID, and retrieve it
- **GitHub Actions workflow** — run ScanAPI after deploy and upload the HTML report as an artifact

## Run locally

```bash
pip install scanapi
source .env
scanapi run scanapi.yaml -c scanapi.conf
```

The report will be available at `scanapi-report.html`.

## Run in CI

Copy `.github/workflows/smoke-tests.yml` into your project. The workflow:

1. Installs Python and ScanAPI
2. Runs the spec against your deployed service
3. Uploads the HTML report as a build artifact

### Trigger manually

```bash
gh workflow run smoke-tests.yml -f base_url=https://your-api.example.com
```

### Call from a deploy workflow

```yaml
jobs:
deploy:
# ... your deploy steps ...

smoke-test:
needs: [deploy]
uses: ./.github/workflows/smoke-tests.yml
with:
base_url: https://your-api.example.com
```

## Authenticated APIs

The workflow includes a commented-out job showing how to obtain an OAuth2 token from GitHub Secrets before running the tests. Uncomment and adapt it to your auth provider.

In your `scanapi.yaml`, reference the token as an environment variable:

```yaml
headers:
Authorization: Bearer ${ACCESS_TOKEN}
```

And configure `scanapi.conf` to hide it from the report:

```yaml
report:
hide_request:
headers:
- Authorization
```
7 changes: 7 additions & 0 deletions ci-smoke-tests/scanapi.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
project_name: CI Smoke Tests
output_path: scanapi-report.html
no_report: false
report:
hide_request:
headers:
- Authorization
84 changes: 84 additions & 0 deletions ci-smoke-tests/scanapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
endpoints:
# -------------------------------------------------------
# Health Check
# -------------------------------------------------------
- name: Health Check
path: ${BASE_URL}
requests:
- name: service_is_reachable
method: get
tests:
- name: returns_200
assert: ${{ response.status_code == 200 }}

# -------------------------------------------------------
# Posts API — Read operations
# -------------------------------------------------------
- name: Posts API
path: ${BASE_URL}/posts
headers:
Content-Type: application/json
requests:
- name: list_posts
method: get
params:
_limit: 2
tests:
- name: returns_200
assert: ${{ response.status_code == 200 }}
- name: response_is_list
assert: ${{ isinstance(response.json(), list) }}
- name: respects_limit
assert: ${{ len(response.json()) <= 2 }}

- name: get_single_post
path: /1
method: get
tests:
- name: returns_200
assert: ${{ response.status_code == 200 }}
- name: has_expected_fields
assert: ${{ all(k in response.json() for k in ("id", "title", "body", "userId")) }}

- name: get_nonexistent_post
path: /99999
method: get
tests:
- name: returns_404
assert: ${{ response.status_code == 404 }}

# -------------------------------------------------------
# Resource Lifecycle (request chaining)
# Demonstrates creating a resource and retrieving it
# using variables extracted from the previous response.
# -------------------------------------------------------
- name: Post Lifecycle
path: ${BASE_URL}/posts
headers:
Content-Type: application/json
requests:
- name: create_post
method: post
body:
title: "ScanAPI Smoke Test"
body: "This post was created by a CI smoke test."
userId: 1
tests:
- name: returns_201
assert: ${{ response.status_code == 201 }}
- name: has_id
assert: ${{ "id" in response.json() }}
- name: title_matches
assert: ${{ response.json()["title"] == "ScanAPI Smoke Test" }}
vars:
created_post_id: ${{ response.json()["id"] }}

- name: get_created_post
path: /${created_post_id}
method: get
tests:
- name: returns_200_or_404
# JSONPlaceholder doesn't persist new posts, so the
# created ID may return 404. In a real API this would
# be a strict 200 check. We keep it to show the pattern.
assert: ${{ response.status_code in (200, 404) }}