From 0fe56945656066e5ed39284b8ffe74d1742c78a9 Mon Sep 17 00:00:00 2001
From: ShubhamMour
Date: Tue, 14 Jul 2026 09:48:25 +0530
Subject: [PATCH 1/3] Added Test Plan support, step-by-step results,
screenshots and api version to v2
This update lets you send more of your automated test results to Vansah:
- Run your test cases against Standard and Advanced Test Plans, and choose
which iteration to record the results against.
- Record a result for each individual step of a test case, or a single
overall pass/fail for the whole case.
- Attach screenshots to your test steps so the evidence shows up in Vansah.
- Refreshed the README and setup guide.
---
.env.example | 42 +++
.gitignore | 9 +-
DemoUsage.py | 68 ----
README.md | 372 +++++++++++++++-------
VansahNode.py | 749 +++++++++++++++++++++++++++++++++-----------
license.md | 28 ++
test_vansah_node.py | 239 ++++++++++++++
7 files changed, 1136 insertions(+), 371 deletions(-)
create mode 100644 .env.example
delete mode 100644 DemoUsage.py
create mode 100644 license.md
create mode 100644 test_vansah_node.py
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..f16a521
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,42 @@
+# Copy this file to `.env` and fill in real values before running VansahNodeFullTests.
+# `.env` is already listed in .gitignore -- never commit real tokens.
+
+# Base URL of your Vansah instance. Defaults to https://prod.vansah.com if unset.
+VANSAH_URL=
+
+# Vansah Connect token (Vansah Test Management > Connect). Required to run any test --
+# all tests are skipped (not failed) if this is blank.
+VANSAH_TOKEN=
+
+# Jira project key that owns the test folder / issue below, e.g. "DEMO".
+VANSAH_PROJECT_KEY=
+
+# Path of an existing Vansah test folder, e.g. "regression/2025/smoke".
+VANSAH_FOLDER_PATH=
+
+# Key of an existing Jira issue to attach test runs to, e.g. "DEMO-1".
+VANSAH_JIRA_ISSUE_KEY=
+
+# Key of an existing test case, e.g. "DEMO-C1".
+VANSAH_TEST_CASE_KEY=
+
+# Key of an existing Advanced Test Plan, e.g. "DEMO-P1".
+VANSAH_ATP_KEY=
+
+# Asset type backing the Advanced Test Plan's requirement: "folder" or "issue". Defaults to "folder".
+VANSAH_ATP_ASSET_TYPE=
+
+# Key of an existing Standard Test Plan, e.g. "DEMO-P2".
+VANSAH_STP_KEY=
+
+# Optional test run properties.
+VANSAH_SPRINT_NAME=
+VANSAH_RELEASE_NAME=
+VANSAH_ENVIRONMENT_NAME=
+
+# Optional path to a real screenshot/image file to use for attachment tests.
+# If unset, a tiny generated placeholder PNG is used instead.
+VANSAH_SCREENSHOT_PATH=
+
+# Optional: true/1 to print outgoing request payloads (mirrors VansahNode.setDebug()).
+VANSAH_DEBUG=false
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 60323d5..f5c3e43 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
+# macOS system files
+.DS_Store
+
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
@@ -33,9 +36,13 @@ eggs/
*.rej
*.spec
-# Environment variables
+# Environment variables (keep .env.example tracked)
.env
*.env
+!.env.example
+
+# Documentation build artifacts (help-article docx, generated locally)
+docs/
# Unit test / coverage reports
htmlcov/
diff --git a/DemoUsage.py b/DemoUsage.py
deleted file mode 100644
index be0680d..0000000
--- a/DemoUsage.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from VansahNode import VansahNode
-
-# Initialize VansahNode with configuration and test details
-
-# JIRA Test Requirement details
-JIRA_ISSUE_KEY = "TF-1" # JIRA issue key associated with the test requirement
-
-# Vansah Test Case details
-VANSAH_URL = "https://prodau.vansah.com" # Base URL for Vansah API
-TEST_CASE = "TF-C4" # Vansah test case key
-
-# Create an instance of VansahNode
-vansahnode = VansahNode()
-
-# Set up VansahNode configuration
-vansahnode.set_vansah_url(VANSAH_URL) # Set the Vansah API URL
-vansahnode.set_sprint_name("TF Sprint 1") # Specify the sprint name
-vansahnode.set_environment_name("UAT") # Define the environment (e.g., UAT, PROD)
-vansahnode.set_release_name("TestingTRUNK") # Set the release name
-
-# Set the API token for authentication (replace with your actual token)
-vansahnode.set_vansah_token(
- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.**********************************************pHuaoEf9yVhlk"
-)
-
-# Link the JIRA issue key to the test run
-vansahnode.set_jira_issue_key(JIRA_ISSUE_KEY)
-
-# Create a test run for the specified test case
-vansahnode.add_test_run_from_jira_issue(TEST_CASE)
-
-# Add test logs for specific test steps with results and comments
-# Each test step is linked to a screenshot (optional)
-
-# Adding log for step 1
-vansahnode.add_test_log(
- result="passed", # Result of the test step (e.g., passed, failed)
- comment="This is my log", # A comment describing the test step result
- test_step_row=1, # Step number in the test case
- image_path=r"C:\Users\***\Test Asset\******436908.png" # Path to a screenshot (if applicable)
-)
-
-# Adding log for step 2
-vansahnode.add_test_log(
- result="passed",
- comment="This is my log 2",
- test_step_row=2,
- image_path=r"C:\Users\***\Test Asset\******436908.png"
-)
-
-# Optional operations
-# Uncomment these lines if you want to explore additional operations:
-
-# Get the number of test steps in a test case
-# vansahnode.test_step_count(TEST_CASE)
-
-# Add a quick test from a test folder
-# vansahnode.add_quick_test_from_test_folder(TEST_CASE, "passed")
-
-# Remove a specific test log (requires TEST_LOG_IDENTIFIER to be set)
-# vansahnode.remove_test_log()
-
-# Remove the entire test run (requires TEST_RUN_IDENTIFIER to be set)
-# vansahnode.remove_test_run()
-
-# This script demonstrates basic usage of the VansahNode class to manage test cases,
-# test logs, and interactions with the Vansah API. Modify the variables and configurations
-# as needed for your specific use case.
diff --git a/README.md b/README.md
index 5dc4823..7749a50 100644
--- a/README.md
+++ b/README.md
@@ -10,15 +10,12 @@
-> [!WARNING]
-> **⚠️ Upcoming breaking change — v1 API will be discontinued**
+> ⚠️ **API Token Update Required**
>
-> **Vansah API v1** will stop working once Vansah migrates to the Forge platform. This integration will be non-functional after that point until it is updated to use **Vansah API v2**.
-
-> [!NOTE]
-> **🚀 Vansah API v2 — Coming Soon**
+> Existing Vansah Connect tokens must be regenerated to continue using integrations. All Connect-based integrations and bindings must use the Vansah API v2 endpoint (`/api/v2/`). **This binding already targets v2.**
>
-> This repository will be updated with full v2 support before the migration. **Watch this repo** to get notified when the update is released.
+> - Generate a new Vansah API token
+> - Test your integrations to ensure continuity post-release
>
> In the meantime, for more details check out:
> - [Vansah for Jira — May 2026 Forge Release](https://help.vansah.com/en/articles/13298303-vansah-for-jira-may-2026)
@@ -28,185 +25,318 @@
## Table of Contents
-- [Overview](#overview)
- [Features](#features)
+- [Prerequisite](#prerequisite)
- [Installation](#installation)
-- [Setup and Configuration](#setup-and-configuration)
- - [Prerequisites](#prerequisites)
- - [Basic Configuration](#basic-configuration)
-- [Usage](#usage)
- - [1. Create a Test Run from Jira Issue](#1-create-a-test-run-from-jira-issue)
- - [2. Log Test Results](#2-log-test-results)
- - [3. Manage Test Artifacts](#3-manage-test-artifacts)
-- [Use Case: Sending Test Results from Automation Framework to Vansah](#use-case-sending-test-results-from-automation-framework-to-vansah)
- - [Example: Integrating Selenium with Vansah](#example-integrating-selenium-with-vansah)
+- [Configuration](#configuration)
+- [Usage examples](#usage-examples)
+ - [Step-by-step execution against a Jira Issue](#step-by-step-execution-against-a-jira-issue)
+ - [Folder Path, Advanced Test Plan (ATP), and Standard Test Plan (STP)](#folder-path-advanced-test-plan-atp-and-standard-test-plan-stp)
+- [Methods Overview](#methods-overview)
+- [Setter Methods of Vansah Binding](#setter-methods-of-vansah-binding)
+- [Documentation](#documentation)
+- [Running the tests](#running-the-tests)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [Developed By](#developed-by)
---
-## Overview
-`VansahNode` is a Python library that allows you to interact with the Vansah Test Management system for Jira. This library helps automate the process of adding test runs, logging test results, and managing test artifacts in Vansah directly from your automation framework.
-
## Features
-- Create test runs from Jira issues or test folders.
-- Log test results for individual test steps.
-- Attach screenshots or other artifacts to test logs.
-- Manage test runs and logs programmatically.
+
+- Set a custom API URL and token for authentication.
+- Easily connect your Python applications with `Vansah Test Management for Jira` to report test results and update test runs without manual intervention.
+- Report results as a single overall verdict (Quick Test) or **step by step**, against a **Jira issue**, a **test folder**, a **Standard Test Plan**, or an **Advanced Test Plan**.
+- **Iterate** over every step of a test case — a run is created `Untested` so Vansah pre-creates one log per step, and each result updates the correct step log in place.
+- Target a specific **Test Plan iteration** (1–5).
+- Attach **screenshots** to test steps for more detailed reporting and analysis.
+- Request-payload logging (`set_debug`) to troubleshoot integration issues; the token is sent as a header and is never printed.
+- Detailed documentation and usage examples to help you get started quickly.
+
+---
+
+## Prerequisite
+
+- Make sure that [`Vansah`](https://marketplace.atlassian.com/apps/1224250/vansah-test-management-for-jira?tab=overview&hosting=cloud) is installed in your Jira workspace.
+- Generate a Vansah [`connect`](https://help.vansah.com/en/articles/9824979-generate-a-vansah-api-token-from-jira) token to authenticate with the Vansah APIs.
+- [Python 3.8+](https://www.python.org/downloads/) installed for Windows, Linux, or macOS.
+- The `requests` library (see [Installation](#installation)).
---
## Installation
-To get started with `VansahNode`, ensure you have [Python 3.12+](https://www.python.org/downloads/) installed for Windows, Linux and MacOS. Then, install the required modules:
+
+Install the required module:
```bash
pip install requests
```
-You can also install other dependencies (if applicable):
+Or, if a `requirements.txt` is provided:
```bash
pip install -r requirements.txt
```
+Then copy [`VansahNode.py`](/VansahNode.py) into your test project and import it:
+
+```python
+from VansahNode import VansahNode
+```
+
---
-## Setup and Configuration
-### Prerequisites
-- **Vansah API Token**: Obtain your [API token](https://help.vansah.com/en/articles/9824979-generate-a-vansah-api-token-from-jira) from your Jira workspace.
-- **Vansah URL**: The base URL for your Vansah instance (e.g., `https://prod.vansah.com`) or Obtain your [Vansah Connect URL](https://help.vansah.com/en/articles/10407923-vansah-api-connect-url) from Vansah API Tokens
-.
+## Configuration
-### Basic Configuration
-Set up your `VansahNode` instance by configuring the following:
+Provide your Vansah [`connect`](https://help.vansah.com/en/articles/9824979-generate-a-vansah-api-token-from-jira) token. You have two options:
-1. **Vansah URL**
-2. **Sprint, Environment, and Release details**
-3. **API Token**
-4. **Jira Issue Key or Test Folder ID**
+- Set it directly on the instance:
-Example:
+ ```python
+ vansah = VansahNode()
+ vansah.set_vansah_token("Add your Token here")
-```python
-from VansahNode import VansahNode
+ # Optional: set a custom API URL (from Vansah Settings > Vansah API Tokens)
+ vansah.set_vansah_url("https://")
+ ```
+
+- Or read it from an environment variable so it never lands in source control:
-# Initialize the VansahNode instance
-vansahnode = VansahNode()
+ ```python
+ import os
+ vansah = VansahNode()
+ vansah.set_vansah_token(os.environ.get("VANSAH_TOKEN"))
+ ```
-# Set up configuration
-vansahnode.set_vansah_url("https://prod.vansah.com")
-vansahnode.set_vansah_token("")
-vansahnode.set_sprint_name("Sprint 1")
-vansahnode.set_environment_name("UAT")
-vansahnode.set_release_name("Release 1")
-vansahnode.set_jira_issue_key("PROJECT-123")
+A minimal configuration looks like:
+
+```python
+from VansahNode import VansahNode
-#Add Project Key in case of Test Folder runs
-vansahnode.set_project_key("PROJECT")
+vansah = VansahNode()
+vansah.set_vansah_url("https://prod.vansah.com")
+vansah.set_vansah_token("")
+vansah.set_project_key("DEMO") # Space Key (Jira project key) — required for API v2
+vansah.set_jira_issue_key("DEMO-1")
+vansah.set_test_folders_path("regression/login/")
+vansah.set_sprint_name("Sprint 1")
+vansah.set_environment_name("UAT")
+vansah.set_release_name("Release 1")
```
---
-## Usage
+## Usage examples
-### 1. Create a Test Run from Jira Issue
-Use the `add_test_run_from_jira_issue` method to create a test run linked to a Jira issue.
+### Step-by-step execution against a Jira Issue
```python
-# Create a test run for a specific test case
-TEST_CASE = "PROJECT-TC1"
-vansahnode.add_test_run_from_jira_issue(TEST_CASE)
-```
+from VansahNode import VansahNode
-### 2. Log Test Results
-Log results for specific test steps within a test run.
+vansah = VansahNode()
+vansah.set_vansah_token("")
+vansah.set_project_key("DEMO") # Space Key (Jira project key) — required for API v2
+vansah.set_jira_issue_key("DEMO-1") # the work item this run is reported against
+vansah.set_environment_name("QA")
-```python
-# Log a test result with a screenshot
-vansahnode.add_test_log(
- result="passed", # Result: "passed", "failed", etc.
- comment="Test step executed successfully.",
- test_step_row=1, # Step number
- image_path=r"path/to/screenshot.png" # Optional screenshot
-)
+# Start a run for the test case (created Untested; results come from the step logs)
+vansah.add_test_run_from_jira_issue("DEMO-C1")
+
+# Log a result for each step of the test case
+try:
+ # add_test_log(result, comment, step_number, screenshot_path=optional)
+ vansah.add_test_log("passed", "Step 1 executed successfully", 1)
+ vansah.add_test_log("passed", "Step 2 executed successfully", 2)
+except Exception as e:
+ # Update the step with a failure and a screenshot when something goes wrong
+ vansah.update_test_log("failed", f"Step failed: {e}", r"path/to/screenshot.png")
```
-### 3. Manage Test Artifacts
-- **Remove a Test Run**:
+### Folder Path, Advanced Test Plan (ATP), and Standard Test Plan (STP)
```python
-vansahnode.remove_test_run() # Requires TEST_RUN_IDENTIFIER to be set
-```
+from VansahNode import VansahNode
-- **Remove a Test Log**:
+vansah = VansahNode()
+vansah.set_vansah_url("https://prod.vansah.com")
+vansah.set_vansah_token("")
+vansah.set_project_key("KAN") # Space Key (Jira project key)
+vansah.set_test_folders_path("vansah test automation/regression 2025/")
+vansah.set_advanced_test_plan_key("KAN-P17") # Advanced Test Plan key
+vansah.set_standard_test_plan_key("KAN-P18") # Standard Test Plan key
+# vansah.set_test_plan_iteration(2) # optional — defaults to iteration 1
-```python
-vansahnode.remove_test_log() # Requires TEST_LOG_IDENTIFIER to be set
+TEST_CASE = "KAN-C17"
+
+# Test folder
+vansah.add_test_run_from_test_folder(TEST_CASE)
+vansah.add_test_log("passed", "Actual result for the Test Step", 1)
+
+# Advanced Test Plan (ATP) — asset type is "folder" or "issue"
+vansah.add_test_run_from_advanced_test_plan("folder", TEST_CASE)
+vansah.add_test_log("passed", "Actual result for the Test Step", 1)
+
+# Standard Test Plan (STP)
+vansah.add_test_run_from_standard_test_plan(TEST_CASE)
+vansah.add_test_log("passed", "Actual result for the Test Step", 1)
```
---
-## Use Case: Sending Test Results from Automation Framework to Vansah
+## Methods Overview
-### Example: Integrating Selenium with Vansah
-Here’s how you can send test results from a Selenium test script to Vansah:
+The `VansahNode` class provides a comprehensive interface for interacting with Vansah Test Management for Jira directly from Python. Below is a description of its public methods.
-```python
-from selenium import webdriver
-from VansahNode import VansahNode
+### `add_test_run_from_jira_issue(testcase)`
-# Initialize VansahNode
-vansahnode = VansahNode()
-vansahnode.set_vansah_url("https://prod.vansah.com")
-vansahnode.set_vansah_token("")
-vansahnode.set_sprint_name("Sprint 1")
-vansahnode.set_environment_name("UAT")
-vansahnode.set_release_name("Release 1")
-vansahnode.set_jira_issue_key("PROJECT-123")
-
-# Set up Selenium
-driver = webdriver.Chrome()
-try:
- driver.get("https://example.com")
- assert "Example Domain" in driver.title
-
- # Create a test run
- TEST_CASE = "PROJECT-TC1"
- vansahnode.add_test_run_from_jira_issue(TEST_CASE)
-
- # Log the result
- vansahnode.add_test_log(
- result="passed",
- comment="Homepage loaded successfully.",
- test_step_row=1,
- image_path="path/to/screenshot.png"
- )
-except Exception as e:
- vansahnode.add_test_log(
- result="failed",
- comment=f"Test failed with error: {e}",
- test_step_row=1
- )
-finally:
- driver.quit()
+Creates a new test run linked to a specific Jira issue (set via `set_jira_issue_key`). The run is created `Untested`, with a log pre-created for each step; record each step's result with `add_test_log`.
+
+- **Parameters**:
+ - `testcase`: The test case key (e.g., `"DEMO-C1"`).
+
+### `add_test_run_from_test_folder(testcase)`
+
+Creates a new test run against a test folder (set the folder path via `set_test_folders_path`). The run is created `Untested`, with a log pre-created for each step.
+
+- **Parameters**:
+ - `testcase`: The test case key (e.g., `"KAN-C17"`).
+
+### `add_test_run_from_standard_test_plan(testcase)`
+
+Creates a new test run under a Standard Test Plan. Set the plan key first with `set_standard_test_plan_key(...)`. The run targets **iteration 1** by default; call `set_test_plan_iteration(...)` (range 1–5) beforehand to target a different iteration.
+
+- **Parameters**:
+ - `testcase`: The test case key to execute under the plan (e.g., `"KAN-C17"`).
+
+### `add_test_run_from_advanced_test_plan(test_plan_asset_type, testcase)`
+
+Creates a new test run under an Advanced Test Plan. Set the plan key first with `set_advanced_test_plan_key(...)`. Because a case can sit under more than one requirement, pass the requirement's asset type and set its matching key. The run targets **iteration 1** by default; call `set_test_plan_iteration(...)` beforehand to change it.
+
+- **Parameters**:
+ - `test_plan_asset_type`: The requirement the case runs under — `"folder"` (uses `set_test_folders_path`) or `"issue"` (uses `set_jira_issue_key`).
+ - `testcase`: The test case key to execute (e.g., `"KAN-C17"`).
+
+### `add_test_log(result, comment, test_step_row, image_path=None)`
+
+Records the result and actual outcome for a single step of the current test run. Call one of the `add_test_run_*` methods first.
+
+- **Parameters**:
+ - `result`: Step result — as a name (`"passed"`, `"failed"`, `"na"`, `"untested"` — case-insensitive) or a code (`2`, `1`, `0`, `3`).
+ - `comment`: Actual result text shown against the step.
+ - `test_step_row`: 1-based step number within the test case.
+ - `image_path` *(optional)*: Path to an image file to attach as evidence.
+
+### `update_test_log(result, comment, image_path=None)`
+
+Updates the most recently recorded step log with a new result, comment, and optionally a screenshot.
+
+### `add_quick_test_from_jira_issue(testcase, result)` and `add_quick_test_from_test_folder(testcase, result)`
+
+Creates a run and records a single overall result in one call — useful when a test is pass/fail as a whole and has no steps to report individually.
+
+- **Parameters**:
+ - `testcase`: The test case key.
+ - `result`: The overall result, as a name or a code.
+
+### `remove_test_run()` and `remove_test_log()`
+
+Deletes a previously created test run or the most recent test log. After a log is removed, the step keeps an `Untested` placeholder so it can be recorded again later.
+
+---
+
+## Setter Methods of Vansah Binding
+
+Configure the test context before creating runs or logs.
+
+### `set_vansah_token(vansah_token)`
+
+Sets the Vansah Connect token used to authenticate every request. Read it from an environment variable to keep it out of source control.
+
+### `set_vansah_url(vansah_url)`
+
+Sets a custom Vansah API URL. Obtain your Vansah Connect URL from **Vansah Settings > Vansah API Tokens**. If not set, the binding uses its default host (`https://prod.vansah.com`).
+
+### `set_project_key(project_key)`
+
+Sets the **Space Key** (the Jira project key) that scopes your test runs and logs. This is **required for the Vansah API v2** — it is sent as the top-level `project` object on every request (e.g., `"KAN"`). A null/empty value is ignored with a warning.
+
+### `set_test_folders_path(testfolder_path)`
+
+Sets the **Test Folder path** used by `add_test_run_from_test_folder` (and by an Advanced Test Plan run when its requirement is a folder), e.g., `"regression/login/"`. The path must contain at least one `/` and must not start with `/`.
+
+### `set_jira_issue_key(jira_issue_key)`
+
+Sets the Jira issue key used by `add_test_run_from_jira_issue` (and by an Advanced Test Plan run when its requirement is an issue).
+
+### `set_sprint_name(...)`, `set_release_name(...)`, `set_environment_name(...)`
+
+Optional run properties — the sprint, release/version, and environment (e.g., `"SYS"`, `"UAT"`) recorded against the run.
+
+### `set_standard_test_plan_key(test_plan_key)`
+
+Sets the Standard Test Plan key that `add_test_run_from_standard_test_plan(...)` runs against (e.g., `"KAN-P18"`). A null/empty value is ignored with a warning.
+
+### `set_advanced_test_plan_key(test_plan_key)`
+
+Sets the Advanced Test Plan key that `add_test_run_from_advanced_test_plan(...)` runs against (e.g., `"KAN-P17"`). A null/empty value is ignored with a warning.
+
+### `set_test_plan_iteration(iteration)`
+
+Sets the **iteration** to target when creating a run from a Standard or Advanced Test Plan. This is **optional** — if you never call it, runs default to **iteration 1**. Only call it when recording results against a specific iteration.
+
+- **Parameters**:
+ - `iteration`: The iteration to target. Valid range is **1–5**. Values outside this range are ignored (a warning is printed and the default of 1 is kept).
+
+> **Note:** The iteration applies only to `add_test_run_from_standard_test_plan(...)` and `add_test_run_from_advanced_test_plan(...)`. It has no effect on Jira-issue or test-folder runs.
+
+### `set_debug(debug)`
+
+Enables or disables **request-payload logging**. When enabled, the JSON body sent to Vansah is printed before each API call — useful for diagnosing a malformed folder path or a missing Space Key. The token is sent as a header and is never printed; screenshot attachments are omitted from the log. Debug logging is also enabled automatically when the `VANSAH_DEBUG` environment variable is set to `true` or `1`.
+
+- **Parameters**:
+ - `debug`: `True` to log outgoing request payloads, `False` to disable.
+
+---
+
+## Documentation
+
+A step-by-step help article is available in [`docs/How-to-send-test-results-to-Vansah-from-Python.docx`](/docs/How-to-send-test-results-to-Vansah-from-Python.docx).
+
+---
+
+## Running the tests
+
+[`test_vansah_node.py`](/test_vansah_node.py) contains a full integration suite. It is driven entirely by a local `.env` file (copy `.env` and fill in real values) and every network test is **skipped** (not failed) when `VANSAH_TOKEN` is blank, so it is safe to run without credentials.
+
+```bash
+pip install requests pytest
+pytest -v test_vansah_node.py
```
+`.env` is already listed in `.gitignore` — never commit a real token.
+
---
## Troubleshooting
-1. **Error: `TEST_RUN_IDENTIFIER is not set`**
- - Ensure you call `add_test_run_from_jira_issue` or `add_test_run_from_test_folder` before logging test results.
-2. **Error: Authentication Failed**
- - Verify your API token and Vansah URL.
+1. **`TEST_RUN_IDENTIFIER is not set` / "Please start a test run before recording a step result"**
+ - Ensure you call one of the `add_test_run_*` methods before logging test results.
+
+2. **Authentication failed / `JWT claim did not contain the issuer (iss)`**
+ - Regenerate your Vansah Connect token and confirm the binding is targeting **API v2** (it is by default). Verify your token and Vansah URL.
-3. **File Not Found Error**
- - Check the file path provided for screenshots or other artifacts.
+3. **`Failed to validate asset from request` (errorCode 1306)**
+ - Check the folder path: it must not start with `/` and must contain at least one `/`. Enable `set_debug(True)` to inspect the outgoing payload.
+
+4. **File Not Found for screenshots**
+ - Check the file path provided for the screenshot. Invalid paths are skipped with a warning.
+
+Turn on `set_debug(True)` (or set `VANSAH_DEBUG=1`) to print each outgoing request payload while diagnosing issues.
---
## Contributing
+
We welcome contributions! Please feel free to submit issues or pull requests to improve this library.
---
diff --git a/VansahNode.py b/VansahNode.py
index 603e9b2..8e1df66 100644
--- a/VansahNode.py
+++ b/VansahNode.py
@@ -1,23 +1,50 @@
-from http.client import responses
-
+import os
import requests
import base64
-import json
+
class VansahNode:
"""
- A Python class for interacting with the Vansah API.
- Provides methods to add, update, and remove test runs and logs.
+ A Python class for interacting with the Vansah API (v2).
+
+ Provides methods to create test runs (from a Jira issue, a test folder, a
+ Standard Test Plan, or an Advanced Test Plan), record step-by-step results,
+ attach screenshots, run quick tests, and update or remove runs and logs.
+
+ A run is created as ``UNTESTED`` so Vansah pre-creates one log per test step;
+ :meth:`add_test_log` then updates the correct step log in place instead of
+ creating duplicates.
"""
- API_VERSION = "v1"
- VANSAH_URL = "https://prod.vansahnode.app"
+
+ # The API version used for the Vansah API calls.
+ # v2 is REQUIRED for Vansah Connect tokens; the deprecated v1 path rejects them.
+ API_VERSION = "v2"
+
+ # The default base URL of the Vansah API.
+ # Note: For Data Residency support you can set this to your region URL.
+ # https://help.vansah.com/en/articles/10407923-vansah-api-connect-url
+ DEFAULT_VANSAH_URL = "https://prod.vansah.com"
+ VANSAH_URL = DEFAULT_VANSAH_URL
+
VANSAH_TOKEN = "Your Vansah Connect Token"
+
+ # "1" sends results to Vansah, "0" disables all outgoing calls.
updateVansah = "1"
+ # Result name -> numeric id mapping used by the Vansah API (v2 expects an id).
+ RESULT_AS_NAME = {
+ "NA": 0,
+ "FAILED": 1,
+ "PASSED": 2,
+ "UNTESTED": 3,
+ }
+
+ # Creating a run as UNTESTED (id 3) makes Vansah pre-create one UNTESTED log
+ # per step of the test case; those per-step logs are then updated by add_test_log.
+ UNTESTED_RESULT_ID = 3
+
def __init__(self):
- """
- Initializes the VansahNode instance.
- """
+ """Initializes the VansahNode instance."""
self.TESTFOLDER_PATH = None
self.JIRA_ISSUE_KEY = None
self.PROJECT_KEY = None
@@ -34,39 +61,66 @@ def __init__(self):
self.FILE = None
self.image = None
self.headers = None
- self.resultAsName = None
+
+ # Test Plan configuration.
+ self.STANDARD_TEST_PLAN_KEY = None
+ self.ADVANCED_TEST_PLAN_KEY = None
+ self.TEST_PLAN_ITERATION = None # None -> API default (iteration 1)
+ self.TEST_PLAN_ASSET_TYPE = None # "folder" or "issue" (Advanced Test Plan)
+
+ # Maps a 1-based step number to the identifier of its pre-created log for
+ # the current run, so add_test_log updates the correct step log.
+ self.step_log_identifiers = {}
+
+ # Request-payload logging. Enabled automatically when VANSAH_DEBUG is
+ # set to "true" or "1"; toggle at runtime with set_debug().
+ self.DEBUG = os.environ.get("VANSAH_DEBUG", "").lower() in ("true", "1")
+
+ # --------------------------------------------------------------------- #
+ # Configuration setters
+ # --------------------------------------------------------------------- #
@staticmethod
def set_vansah_url(vansah_url):
"""
- Sets the base URL for the Vansah API.
+ Sets the base URL for the Vansah API. Falls back to the default URL if
+ a null/empty value is provided.
:param vansah_url: The base URL to set.
"""
- VansahNode.VANSAH_URL = vansah_url
+ if vansah_url and vansah_url.strip():
+ VansahNode.VANSAH_URL = vansah_url.strip()
+ else:
+ VansahNode.VANSAH_URL = VansahNode.DEFAULT_VANSAH_URL
+ print("⚠️ Invalid VANSAH_URL. Resetting to default:", VansahNode.VANSAH_URL)
def set_vansah_token(self, vansah_token):
"""
Sets the authentication token for the Vansah API.
- :param vansah_token: The token to set.
+ :param vansah_token: The Vansah Connect token to set.
"""
self.VANSAH_TOKEN = vansah_token
def set_project_key(self, project_key):
"""
- Sets the project key in the Request.
+ Sets the Space Key (the Jira project key) that scopes your test runs and
+ logs. Required for the Vansah API v2 — it is sent as the top-level
+ ``project`` object on every request.
- :param project_key: The token to set.
+ :param project_key: The Space Key / Jira project key (e.g., "DEMO").
"""
- self.PROJECT_KEY = project_key
-
+ if project_key and project_key.strip():
+ self.PROJECT_KEY = project_key.strip()
+ else:
+ print("⚠️ Warning: Provided Space Key is null or empty. Value not updated.")
def set_test_folders_path(self, testfolder_path):
"""
- Sets the test folder ID.
+ Sets the Test Folder path used by :meth:`add_test_run_from_test_folder`
+ (and by an Advanced Test Plan run whose requirement is a folder).
- :param testfolder_path: The test folder ID to set.
+ :param testfolder_path: The test folder path (e.g., "regression/login/").
"""
self.TESTFOLDER_PATH = testfolder_path
@@ -79,46 +133,156 @@ def set_jira_issue_key(self, jira_issue_key):
self.JIRA_ISSUE_KEY = jira_issue_key
def set_sprint_name(self, sprint_name):
+ """Sets the sprint name."""
+ self.SPRINT_NAME = sprint_name
+
+ def set_release_name(self, release_name):
+ """Sets the release name."""
+ self.RELEASE_NAME = release_name
+
+ def set_environment_name(self, environment_name):
+ """Sets the environment name."""
+ self.ENVIRONMENT_NAME = environment_name
+
+ def set_standard_test_plan_key(self, test_plan_key):
"""
- Sets the sprint name.
+ Sets the Standard Test Plan key that
+ :meth:`add_test_run_from_standard_test_plan` runs against (e.g., "DEMO-P9").
+ A null/empty value is ignored with a warning.
- :param sprint_name: The sprint name to set.
+ :param test_plan_key: The Standard Test Plan key.
"""
- self.SPRINT_NAME = sprint_name
+ if test_plan_key and test_plan_key.strip():
+ self.STANDARD_TEST_PLAN_KEY = test_plan_key.strip()
+ else:
+ print("⚠️ Warning: Provided Standard Test Plan Key is null or empty. Value not updated.")
- def set_release_name(self, release_name):
+ def set_advanced_test_plan_key(self, test_plan_key):
"""
- Sets the release name.
+ Sets the Advanced Test Plan key that
+ :meth:`add_test_run_from_advanced_test_plan` runs against (e.g., "DEMO-P8").
+ A null/empty value is ignored with a warning.
- :param release_name: The release name to set.
+ :param test_plan_key: The Advanced Test Plan key.
"""
- self.RELEASE_NAME = release_name
+ if test_plan_key and test_plan_key.strip():
+ self.ADVANCED_TEST_PLAN_KEY = test_plan_key.strip()
+ else:
+ print("⚠️ Warning: Provided Advanced Test Plan Key is null or empty. Value not updated.")
- def set_environment_name(self, environment_name):
+ def set_test_plan_iteration(self, iteration):
"""
- Sets the environment name.
+ Sets the iteration to target when running against a Standard or Advanced
+ Test Plan. Optional — runs default to iteration 1. Only affects the two
+ test-plan run methods, not issue or folder runs.
- :param environment_name: The environment name to set.
+ :param iteration: The iteration to target. Valid range is 1-5; values
+ outside it are ignored (a warning is printed and the default of 1 is kept).
"""
- self.ENVIRONMENT_NAME = environment_name
+ if isinstance(iteration, int) and 1 <= iteration <= 5:
+ self.TEST_PLAN_ITERATION = iteration
+ else:
+ print("⚠️ Warning: Test Plan iteration must be between 1 and 5. Keeping the default of 1.")
+
+ def set_debug(self, debug):
+ """
+ Enables or disables request-payload logging. When enabled, the JSON body
+ sent to Vansah is printed before each API call — useful for diagnosing a
+ malformed folder path or a missing Space Key. The token is sent as a
+ header and is never printed; screenshot attachments are omitted from the log.
+
+ :param debug: True to log outgoing request payloads, False to disable.
+ """
+ self.DEBUG = bool(debug)
+
+ # --------------------------------------------------------------------- #
+ # Helpers
+ # --------------------------------------------------------------------- #
def encode_file_to_base64(self, file_path):
"""
Encodes a file to a Base64 string.
:param file_path: The path to the file to encode.
- :return: The Base64 string of the file.
+ :return: The Base64 string of the file, or None on error.
"""
try:
with open(file_path, "rb") as file:
- return base64.b64encode(file.read()).decode('utf-8')
+ return base64.b64encode(file.read()).decode("utf-8")
except Exception as e:
print(f"Error encoding file to Base64: {e}")
return None
+ def is_valid_folder_path(self, folder_path):
+ """
+ Validates the Test Folder path:
+ - It must not be null or empty.
+ - It must not start with a forward slash (/).
+ - It must contain at least one forward slash (/).
+
+ :param folder_path: The test folder path to validate.
+ :return: True if valid, otherwise False (a warning is printed).
+ """
+ if not folder_path:
+ print("⚠️ Warning: Folder path cannot be null or empty.")
+ return False
+ if folder_path.startswith("/"):
+ print("⚠️ Warning: Folder path should not start with '/'.")
+ return False
+ if "/" not in folder_path:
+ print("⚠️ Warning: Folder path must contain at least one '/'.")
+ return False
+ return True
+
+ def _validate_screenshot_file(self, image_path):
+ """
+ Validates a screenshot path. On success sets SEND_SCREENSHOT and encodes
+ the file; on failure disables the screenshot for this call.
+
+ :param image_path: Path to the screenshot file.
+ """
+ if image_path and os.path.isfile(image_path):
+ self.FILE = self.encode_file_to_base64(image_path)
+ self.image = image_path
+ self.SEND_SCREENSHOT = self.FILE is not None
+ else:
+ self.SEND_SCREENSHOT = False
+ print("The Screenshot file provided does not exist or is not a file.")
+
+ def _resolve_result(self, result):
+ """
+ Accepts a result as either an int id (0=N/A, 1=Fail, 2=Pass, 3=Untested)
+ or a case-insensitive name ("NA", "FAILED", "PASSED", "UNTESTED") and
+ returns the numeric id. Unknown names default to 0 (N/A).
+
+ :param result: The result as an int or a string.
+ :return: The numeric result id.
+ """
+ if isinstance(result, int):
+ return result
+ return VansahNode.RESULT_AS_NAME.get(str(result).upper(), 0)
+
+ def _emit_payload(self, endpoint, payload):
+ """Prints an outgoing request payload when debug is enabled. Base64 file
+ data in attachments is redacted so the log is not flooded."""
+ if not self.DEBUG:
+ return
+ safe = payload
+ try:
+ if isinstance(payload, dict) and payload.get("attachments"):
+ safe = dict(payload)
+ safe["attachments"] = [
+ {**{k: v for k, v in a.items() if k != "file"},
+ "file": f""}
+ for a in payload["attachments"]
+ ]
+ except Exception:
+ safe = payload
+ print(f"🐛 [DEBUG] Request to {endpoint}: {safe}")
+
def properties(self):
"""
- Constructs the properties JSON object.
+ Constructs the run properties (sprint, release, environment).
:return: A dictionary of properties.
"""
@@ -133,225 +297,448 @@ def properties(self):
def test_case(self):
"""
- Constructs the test case JSON object.
+ Constructs the test case object.
:return: A dictionary with the test case key.
"""
if self.CASE_KEY:
return {"key": self.CASE_KEY}
- else:
- print("Please provide a valid TestCase Key")
- return {}
+ print("Please provide a valid TestCase Key")
+ return {}
+
+ def project_asset(self):
+ """
+ Constructs the top-level project (Space Key) scope. Required for Vansah
+ Connect tokens. Returns an empty dict when no project key is set.
+
+ :return: A dictionary with the project key, or empty.
+ """
+ if self.PROJECT_KEY:
+ return {"key": self.PROJECT_KEY}
+ return {}
+
+ def jira_issue_asset(self):
+ """Constructs the Jira issue asset object."""
+ if self.JIRA_ISSUE_KEY:
+ return {"type": "issue", "key": self.JIRA_ISSUE_KEY}
+ print("Please Provide Valid JIRA Issue Key")
+ return {}
+
+ def test_folder_asset(self):
+ """Constructs the test folder asset object."""
+ if self.TESTFOLDER_PATH:
+ self.is_valid_folder_path(self.TESTFOLDER_PATH)
+ return {"type": "folder", "folderPath": self.TESTFOLDER_PATH}
+ print("Please Provide Valid TestFolder Path")
+ return {}
+
+ def planned_run_asset(self, test_plan_key):
+ """
+ Constructs the "plannedRun" asset used for Standard/Advanced Test Plan runs.
+
+ :param test_plan_key: The Standard or Advanced Test Plan key.
+ :return: A dictionary with the plannedRun type, key, and iteration.
+ """
+ if test_plan_key and len(test_plan_key) >= 2:
+ return {
+ "type": "plannedRun",
+ "key": test_plan_key,
+ "iteration": self.TEST_PLAN_ITERATION if self.TEST_PLAN_ITERATION else 1,
+ }
+ print("Please Provide a Valid Test Plan Key")
+ return {}
+
+ def test_plan_asset(self):
+ """
+ Constructs the requirement backing an Advanced Test Plan (a folder path
+ or an issue key), based on TEST_PLAN_ASSET_TYPE.
+
+ :return: A dictionary describing the requirement asset.
+ """
+ if str(self.TEST_PLAN_ASSET_TYPE).lower() == "issue":
+ return {"type": "issue", "key": self.JIRA_ISSUE_KEY}
+ # default: folder
+ return {"type": "folder", "folderPath": self.TESTFOLDER_PATH}
def result_obj(self, result):
"""
- Constructs the result object.
+ Constructs the result object. The Vansah API v2 expects an id.
+
+ :param result: The numeric result id.
+ :return: A dictionary with the result id.
+ """
+ return {"id": self._resolve_result(result)}
+
+ def add_attachment(self):
+ """Constructs an attachment object from the current screenshot file."""
+ base_name = os.path.basename(self.image or "screenshot.png")
+ name, ext = os.path.splitext(base_name)
+ return {
+ "name": name or "screenshot",
+ "extension": ext.lstrip(".") or "png",
+ "file": self.FILE,
+ }
+
+ def _upload_attachment(self, log_identifier):
+ """
+ Uploads the current screenshot to a test log via the dedicated Vansah v2
+ endpoint ``POST /logs/{identifier}/attachments``. In v2, attachments must
+ be posted to this sub-resource; an ``attachments`` array embedded in the
+ log create/update body is ignored.
- :param result: The result name.
- :return: A dictionary with the result name.
+ :param log_identifier: The test log to attach the screenshot to.
"""
- return {"name": result}
+ if not (self.SEND_SCREENSHOT and self.FILE and log_identifier):
+ return
+ payload = {"attachments": [self.add_attachment()]}
+ endpoint = f"logs/{log_identifier}/attachments"
+ response_data = self.connect_to_vansah_rest(endpoint, method="POST", payload=payload)
+ if response_data and response_data.get("success"):
+ print(f"Screenshot attached to LOG ID: {log_identifier}")
+
+ # --------------------------------------------------------------------- #
+ # Core request
+ # --------------------------------------------------------------------- #
def connect_to_vansah_rest(self, endpoint, method="POST", payload=None):
"""
- Sends a request to the Vansah REST API.
+ Sends a request to the Vansah REST API and returns the parsed response.
- :param endpoint: The API endpoint.
+ :param endpoint: The API endpoint (relative to /api//).
:param method: The HTTP method (default is POST).
:param payload: The request payload.
- :return: The response JSON.
- """
- if VansahNode.updateVansah == "1":
- try:
- self.headers = {
- "Authorization": self.VANSAH_TOKEN,
- "Content-Type": "application/json"
- }
- self.resultAsName = {
- "NA": 0,
- "FAILED": 1,
- "PASSED": 2,
- "UNTESTED": 3
- }
- url = f"{VansahNode.VANSAH_URL}/api/{VansahNode.API_VERSION}/{endpoint}"
- response = requests.request(method, url, headers=self.headers, json=payload)
- response_data = response.json()
-
- if response.status_code == 200 and response_data.get("success"):
- if endpoint == "run" :
- self.TEST_RUN_IDENTIFIER = response_data["data"].get("run", {}).get("identifier")
- if endpoint == "logs" :
- self.TEST_LOG_IDENTIFIER = response_data["data"].get("log", {}).get("identifier")
- print(f"Request to {endpoint} successful: {response_data.get("message")}")
- return response_data
- else:
- print(f"Error from Vansah: {response_data.get('message', 'Unknown error')}")
- except Exception as e:
- print(f"Error connecting to Vansah API: {e}")
+ :return: The response JSON dict, or None.
+ """
+ if VansahNode.updateVansah != "1":
+ print("Sending Test Results to Vansah TM for JIRA is Disabled")
+ return None
+ try:
+ self.headers = {
+ "Authorization": self.VANSAH_TOKEN,
+ "Content-Type": "application/json",
+ }
+ url = f"{VansahNode.VANSAH_URL}/api/{VansahNode.API_VERSION}/{endpoint}"
+ self._emit_payload(url, payload)
+ response = requests.request(method, url, headers=self.headers, json=payload)
+ response_data = response.json()
+
+ if response.status_code == 200 and response_data.get("success"):
+ return response_data
+ print(f"Response From Vansah: {response_data.get('message', 'Unknown error')}")
+ return response_data
+ except Exception as e:
+ print(f"Error connecting to Vansah API: {e}")
+ return None
+
+ def _create_test_run(self, operation, payload):
+ """
+ Sends a run-creation request, stores the run identifier and the per-step
+ log identifiers Vansah pre-creates.
+
+ :param operation: A human-readable label for the run type (for logging).
+ :param payload: The request payload.
+ """
+ response_data = self.connect_to_vansah_rest("run", payload=payload)
+ if response_data and response_data.get("success"):
+ run = response_data.get("data", {}).get("run", {})
+ self.TEST_RUN_IDENTIFIER = run.get("identifier")
+ self._store_step_logs(run)
+ print(f"{operation} created successfully. RUN ID: {self.TEST_RUN_IDENTIFIER}")
+
+ def _store_step_logs(self, run):
+ """
+ Caches the pre-created UNTESTED logs from a run-creation response, keyed
+ by 1-based step number. The create response exposes each log's step as
+ ``step.number``.
+
+ :param run: The "run" object from the response (data.run).
+ """
+ self.step_log_identifiers = {}
+ for log in (run or {}).get("logs", []) or []:
+ number = log.get("step", {}).get("number")
+ identifier = log.get("identifier")
+ if number is not None and identifier is not None:
+ self.step_log_identifiers[int(number)] = identifier
+
+ def _step_for_log(self, log_identifier):
+ """Returns the step number mapped to the given log identifier, or None."""
+ for step, identifier in self.step_log_identifiers.items():
+ if identifier == log_identifier:
+ return step
+ return None
+
+ # --------------------------------------------------------------------- #
+ # Test runs
+ # --------------------------------------------------------------------- #
def add_test_run_from_jira_issue(self, testcase):
"""
- Adds a test run from a JIRA issue.
+ Starts a test run for a test case against a Jira work item (issue).
+ The run is created Untested with an empty log for each step; call
+ :meth:`add_test_log` to record the result of each step.
- :param testcase: The test case identifier.
+ :param testcase: The test case key (e.g., "DEMO-C50").
"""
self.CASE_KEY = testcase
payload = {
"case": self.test_case(),
- "asset": {"type": "issue", "key": self.JIRA_ISSUE_KEY},
- "properties": self.properties()
+ "asset": self.jira_issue_asset(),
+ "result": self.result_obj(VansahNode.UNTESTED_RESULT_ID),
}
- self.connect_to_vansah_rest("run", payload=payload)
+ if self.properties():
+ payload["properties"] = self.properties()
+ if self.project_asset():
+ payload["project"] = self.project_asset()
+ self._create_test_run("Test Run", payload)
+
+ def add_test_run_from_test_folder(self, testcase):
+ """
+ Starts a test run for a test case against a test folder.
+ The run is created Untested with an empty log for each step; call
+ :meth:`add_test_log` to record the result of each step.
+
+ :param testcase: The test case key (e.g., "DEMO-C50").
+ """
+ self.CASE_KEY = testcase
+ payload = {
+ "case": self.test_case(),
+ "asset": self.test_folder_asset(),
+ "result": self.result_obj(VansahNode.UNTESTED_RESULT_ID),
+ }
+ if self.properties():
+ payload["properties"] = self.properties()
+ if self.project_asset():
+ payload["project"] = self.project_asset()
+ self._create_test_run("Test Run", payload)
+
+ def add_test_run_from_standard_test_plan(self, testcase):
+ """
+ Starts a test run for a test case under a Standard Test Plan. Set the plan
+ key first with :meth:`set_standard_test_plan_key`; the run targets
+ iteration 1 unless you call :meth:`set_test_plan_iteration`.
+
+ :param testcase: The test case key (e.g., "DEMO-C50"). Must belong to the plan.
+ """
+ self.CASE_KEY = testcase
+ payload = {
+ "case": self.test_case(),
+ "asset": self.planned_run_asset(self.STANDARD_TEST_PLAN_KEY),
+ "result": self.result_obj(VansahNode.UNTESTED_RESULT_ID),
+ }
+ if self.properties():
+ payload["properties"] = self.properties()
+ if self.project_asset():
+ payload["project"] = self.project_asset()
+ self._create_test_run("Standard Test Plan Run", payload)
+
+ def add_test_run_from_advanced_test_plan(self, test_plan_asset_type, testcase):
+ """
+ Starts a test run for a test case under an Advanced Test Plan. Set the plan
+ key first with :meth:`set_advanced_test_plan_key`; the run targets
+ iteration 1 unless you call :meth:`set_test_plan_iteration`. Because a case
+ can sit under more than one requirement in an advanced plan, pass the
+ requirement's asset type and set its matching key (folder path via
+ :meth:`set_test_folders_path` for "folder", or Jira issue key via
+ :meth:`set_jira_issue_key` for "issue").
+
+ :param test_plan_asset_type: The requirement the case runs under: "folder" or "issue".
+ :param testcase: The test case key (e.g., "DEMO-C50"). Must belong to the plan.
+ """
+ self.TEST_PLAN_ASSET_TYPE = test_plan_asset_type
+ self.CASE_KEY = testcase
+ payload = {
+ "case": self.test_case(),
+ "asset": self.planned_run_asset(self.ADVANCED_TEST_PLAN_KEY),
+ "testPlanAsset": self.test_plan_asset(),
+ "result": self.result_obj(VansahNode.UNTESTED_RESULT_ID),
+ }
+ if self.properties():
+ payload["properties"] = self.properties()
+ if self.project_asset():
+ payload["project"] = self.project_asset()
+ self._create_test_run("Advanced Test Plan Run", payload)
+
+ # --------------------------------------------------------------------- #
+ # Test logs (step-by-step)
+ # --------------------------------------------------------------------- #
def add_test_log(self, result, comment, test_step_row, image_path=None):
"""
- Adds a test log.
+ Records the result and actual outcome for a single step of the current
+ test run. Call one of the ``add_test_run_*`` methods first.
+
+ If the step has a pre-created (Untested) log, it is updated in place;
+ otherwise a fresh log is created for that step.
- :param result: The result name (e.g., 0 = N/A, 1 = FAIL, 2 = PASS, 3 = Not tested).
- :param comment: The comment for the log.
- :param test_step_row: The test step row number.
- :param image_path: Optional path to an image for the log.
+ :param result: Step result as an int (0=N/A, 1=Fail, 2=Pass, 3=Untested)
+ or a case-insensitive name ("passed", "failed", "na", "untested").
+ :param comment: Actual result text shown against the step.
+ :param test_step_row: 1-based step number within the test case.
+ :param image_path: Optional path to a screenshot to attach.
"""
self.RESULT_KEY = result
self.COMMENT = comment
self.STEP_ORDER = test_step_row
+ self.SEND_SCREENSHOT = False
+ if image_path:
+ self._validate_screenshot_file(image_path)
+
+ existing_log_id = self.step_log_identifiers.get(test_step_row)
+
+ if existing_log_id:
+ # A log for this step was pre-created (Untested) when the run was
+ # created. Update it in place rather than posting a new one (which
+ # Vansah rejects with "A Test Log already exists for provided Step.").
+ payload = {
+ "result": self.result_obj(self.RESULT_KEY),
+ "actualResult": self.COMMENT,
+ }
+ endpoint = f"logs/{existing_log_id}"
+ response_data = self.connect_to_vansah_rest(endpoint, method="PUT", payload=payload)
+ if response_data and response_data.get("success"):
+ self.TEST_LOG_IDENTIFIER = existing_log_id
+ self.step_log_identifiers[test_step_row] = existing_log_id
+ print(f"Test Log for step {test_step_row} recorded. LOG ID: {self.TEST_LOG_IDENTIFIER}")
+ self._upload_attachment(self.TEST_LOG_IDENTIFIER)
+ else:
+ # Fallback: no pre-created log for this step -- create one.
+ payload = {
+ "run": {"identifier": self.TEST_RUN_IDENTIFIER},
+ "step": {"number": self.STEP_ORDER},
+ "result": self.result_obj(self.RESULT_KEY),
+ "actualResult": self.COMMENT,
+ }
+ response_data = self.connect_to_vansah_rest("logs", payload=payload)
+ if response_data and response_data.get("success"):
+ self.TEST_LOG_IDENTIFIER = response_data.get("data", {}).get("log", {}).get("identifier")
+ self.step_log_identifiers[test_step_row] = self.TEST_LOG_IDENTIFIER
+ print(f"Test Log for step {test_step_row} recorded. LOG ID: {self.TEST_LOG_IDENTIFIER}")
+ self._upload_attachment(self.TEST_LOG_IDENTIFIER)
+
+ def update_test_log(self, result, comment, image_path=None):
+ """
+ Updates the most recently recorded test log with a new result, comment,
+ and optionally a screenshot.
+ :param result: Result as an int or a case-insensitive name.
+ :param comment: The updated comment.
+ :param image_path: Optional path to a screenshot to attach.
+ """
+ self.RESULT_KEY = result
+ self.COMMENT = comment
+ self.SEND_SCREENSHOT = False
if image_path:
- self.FILE = self.encode_file_to_base64(image_path)
- self.SEND_SCREENSHOT = True
+ self._validate_screenshot_file(image_path)
payload = {
- "run": {"identifier": self.TEST_RUN_IDENTIFIER},
- "step": {"number": self.STEP_ORDER},
"result": self.result_obj(self.RESULT_KEY),
- "actualResult": self.COMMENT
+ "actualResult": self.COMMENT,
}
- if self.SEND_SCREENSHOT:
- payload["attachments"] = [{
- "name": "screenshot",
- "extension": "png",
- "file": self.FILE
- }]
- self.connect_to_vansah_rest("logs", payload=payload)
-
- def add_test_run_from_test_folder(self, testcase):
- """
- Adds a test run from a test folder.
+ endpoint = f"logs/{self.TEST_LOG_IDENTIFIER}"
+ response_data = self.connect_to_vansah_rest(endpoint, method="PUT", payload=payload)
+ if response_data and response_data.get("success"):
+ print(f"Test Log updated successfully. LOG ID: {self.TEST_LOG_IDENTIFIER}")
+ self._upload_attachment(self.TEST_LOG_IDENTIFIER)
- :param testcase: The test case identifier.
- """
- self.CASE_KEY = testcase
- payload = {
- "case": self.test_case(),
- "asset": {"type": "folder", "folderPath": self.TESTFOLDER_PATH},
- "properties": self.properties(),
- "project": {"key": self.PROJECT_KEY}
- }
- self.connect_to_vansah_rest("run", payload=payload)
+ # --------------------------------------------------------------------- #
+ # Quick tests
+ # --------------------------------------------------------------------- #
def add_quick_test_from_jira_issue(self, testcase, result):
"""
- Adds a quick test from a JIRA issue.
+ Creates a run and records a single overall result in one call — useful
+ when a test is pass/fail as a whole and has no steps to report individually.
- :param testcase: The test case identifier.
- :param result: The result name.
+ :param testcase: The test case key.
+ :param result: The overall result as an int or a case-insensitive name.
"""
self.CASE_KEY = testcase
self.RESULT_KEY = result
payload = {
"case": self.test_case(),
- "asset": {"type": "issue", "key": self.JIRA_ISSUE_KEY},
- "properties": self.properties(),
- "result": self.result_obj(self.RESULT_KEY)
+ "asset": self.jira_issue_asset(),
+ "result": self.result_obj(self.RESULT_KEY),
}
- self.connect_to_vansah_rest("run", payload=payload)
+ if self.properties():
+ payload["properties"] = self.properties()
+ if self.project_asset():
+ payload["project"] = self.project_asset()
+ response_data = self.connect_to_vansah_rest("run", payload=payload)
+ if response_data and response_data.get("success"):
+ self.TEST_RUN_IDENTIFIER = response_data.get("data", {}).get("run", {}).get("identifier")
+ print(f"Quick Test: {response_data.get('message')}")
def add_quick_test_from_test_folder(self, testcase, result):
"""
- Adds a quick test from a test folder.
+ Creates a run and records a single overall result in one call, against a
+ test folder.
- :param testcase: The test case identifier.
- :param result: The result name.
+ :param testcase: The test case key.
+ :param result: The overall result as an int or a case-insensitive name.
"""
self.CASE_KEY = testcase
self.RESULT_KEY = result
payload = {
"case": self.test_case(),
- "asset": {"type": "folder", "folderPath": self.TESTFOLDER_PATH},
- "properties": self.properties(),
+ "asset": self.test_folder_asset(),
"result": self.result_obj(self.RESULT_KEY),
- "project":{"key":self.PROJECT_KEY}
}
- self.connect_to_vansah_rest("run", payload=payload)
+ if self.properties():
+ payload["properties"] = self.properties()
+ if self.project_asset():
+ payload["project"] = self.project_asset()
+ response_data = self.connect_to_vansah_rest("run", payload=payload)
+ if response_data and response_data.get("success"):
+ self.TEST_RUN_IDENTIFIER = response_data.get("data", {}).get("run", {}).get("identifier")
+ print(f"Quick Test: {response_data.get('message')}")
+
+ # --------------------------------------------------------------------- #
+ # Remove
+ # --------------------------------------------------------------------- #
def remove_test_run(self):
- """
- Removes a test run.
- """
+ """Removes the current test run."""
endpoint = f"run/{self.TEST_RUN_IDENTIFIER}"
- self.connect_to_vansah_rest(endpoint, method="DELETE")
+ response_data = self.connect_to_vansah_rest(endpoint, method="DELETE")
+ if response_data and response_data.get("success"):
+ print(f"Test Run removed successfully. RUN ID: {self.TEST_RUN_IDENTIFIER}")
def remove_test_log(self):
"""
- Removes a test log.
+ Removes the current test log. After removal, the step keeps an Untested
+ placeholder log so it can be recorded again later.
"""
- endpoint = f"logs/{self.TEST_LOG_IDENTIFIER}"
- self.connect_to_vansah_rest(endpoint, method="DELETE")
+ removed_log_id = self.TEST_LOG_IDENTIFIER
+ endpoint = f"logs/{removed_log_id}"
+ response_data = self.connect_to_vansah_rest(endpoint, method="DELETE")
+ if response_data and response_data.get("success"):
+ print(f"Test Log removed successfully. LOG ID: {removed_log_id}")
+ removed_step = self._step_for_log(removed_log_id)
+ if removed_step is not None:
+ self.step_log_identifiers.pop(removed_step, None)
+ self.TEST_LOG_IDENTIFIER = None
+ if removed_step is not None:
+ self._recreate_untested_step_log(removed_step)
- def update_test_log(self, result, comment, image_path=None):
+ def _recreate_untested_step_log(self, step_number):
"""
- Updates a test log.
+ Posts a fresh Untested log for a step (used after a step's log is removed)
+ and caches its new identifier, so the step keeps a placeholder that a later
+ add_test_log can update.
- :param result: The updated result ID.
- :param comment: The updated comment.
- :param image_path: Optional path to an updated image.
+ :param step_number: The step whose placeholder log should be recreated.
"""
- self.RESULT_KEY = result
- self.COMMENT = comment
-
- if image_path:
- self.FILE = self.encode_file_to_base64(image_path)
- self.SEND_SCREENSHOT = True
-
+ if not self.TEST_RUN_IDENTIFIER:
+ return
payload = {
- "result": self.result_obj(self.RESULT_KEY),
- "actualResult": self.COMMENT
+ "run": {"identifier": self.TEST_RUN_IDENTIFIER},
+ "step": {"number": step_number},
+ "result": self.result_obj(VansahNode.UNTESTED_RESULT_ID),
+ "actualResult": "",
}
-
- if self.SEND_SCREENSHOT:
- payload["attachments"] = [{
- "name": "screenshot",
- "extension": "png",
- "file": self.FILE
- }]
-
- endpoint = f"logs/{self.TEST_LOG_IDENTIFIER}"
- self.connect_to_vansah_rest(endpoint, method="PUT", payload=payload)
-
- def test_step_count(self, case_key):
- """
- Retrieves the count of test steps for a given test case.
-
- :param case_key: The key of the test case.
- :return: The number of test steps.
- """
- try:
- endpoint = "testCase/list/testScripts"
- params = {"caseKey": case_key}
- url = f"{VansahNode.VANSAH_URL}/api/{VansahNode.API_VERSION}/{endpoint}"
- response = requests.get(url, headers=self.headers, params=params)
-
- if response.status_code == 200:
- response_data = response.json()
- if response_data.get("success"):
- steps = response_data.get("data", {}).get("steps", [])
- print(f"Number of steps: {len(steps)}")
- return len(steps)
- else:
- print(f"Error: {response_data.get('message', 'Unknown error')}")
- else:
- print(f"HTTP Error: {response.status_code}")
- except Exception as e:
- print(f"Error retrieving test steps: {e}")
- return 0
\ No newline at end of file
+ response_data = self.connect_to_vansah_rest("logs", payload=payload)
+ if response_data and response_data.get("success"):
+ new_id = response_data.get("data", {}).get("log", {}).get("identifier")
+ if new_id:
+ self.step_log_identifiers[step_number] = new_id
diff --git a/license.md b/license.md
new file mode 100644
index 0000000..30cfc00
--- /dev/null
+++ b/license.md
@@ -0,0 +1,28 @@
+
+

+
+
+ License
+
+
+Copyright (c) 2021-2022 Testpoint, Vansah
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/test_vansah_node.py b/test_vansah_node.py
new file mode 100644
index 0000000..100eebe
--- /dev/null
+++ b/test_vansah_node.py
@@ -0,0 +1,239 @@
+"""
+Full integration tests for the Vansah Python binding (VansahNode).
+
+These tests talk to a real Vansah instance, so they are driven entirely by a
+local `.env` file (copy `.env`, fill in real values). Every test is **skipped**
+(not failed) when `VANSAH_TOKEN` is blank, so the suite is safe to run in CI
+without credentials.
+
+Run with:
+ pip install requests pytest
+ pytest -v test_vansah_node.py
+
+Environment variables (see `.env`):
+ VANSAH_URL, VANSAH_TOKEN, VANSAH_PROJECT_KEY, VANSAH_FOLDER_PATH,
+ VANSAH_JIRA_ISSUE_KEY, VANSAH_TEST_CASE_KEY, VANSAH_ATP_KEY,
+ VANSAH_ATP_ASSET_TYPE, VANSAH_STP_KEY, VANSAH_SPRINT_NAME,
+ VANSAH_RELEASE_NAME, VANSAH_ENVIRONMENT_NAME, VANSAH_SCREENSHOT_PATH,
+ VANSAH_DEBUG
+"""
+import base64
+import os
+
+import pytest
+
+from VansahNode import VansahNode
+
+# --------------------------------------------------------------------------- #
+# .env loading (no external dependency)
+# --------------------------------------------------------------------------- #
+
+
+def _load_dotenv():
+ """Minimal .env parser: KEY=VALUE lines, ignores comments/blank lines and
+ strips surrounding quotes. Does not overwrite an already-set env var."""
+ path = os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env")
+ if not os.path.isfile(path):
+ return
+ with open(path, "r", encoding="utf-8") as f:
+ for line in f:
+ line = line.strip()
+ if not line or line.startswith("#") or "=" not in line:
+ continue
+ key, _, value = line.partition("=")
+ key = key.strip()
+ value = value.strip().strip('"').strip("'")
+ os.environ.setdefault(key, value)
+
+
+_load_dotenv()
+
+
+def env(name, default=""):
+ return os.environ.get(name, default)
+
+
+# --------------------------------------------------------------------------- #
+# Config pulled from the environment
+# --------------------------------------------------------------------------- #
+
+VANSAH_URL = env("VANSAH_URL", "https://prod.vansah.com")
+VANSAH_TOKEN = env("VANSAH_TOKEN")
+PROJECT_KEY = env("VANSAH_PROJECT_KEY")
+FOLDER_PATH = env("VANSAH_FOLDER_PATH")
+JIRA_ISSUE_KEY = env("VANSAH_JIRA_ISSUE_KEY")
+TEST_CASE_KEY = env("VANSAH_TEST_CASE_KEY")
+ATP_KEY = env("VANSAH_ATP_KEY")
+ATP_ASSET_TYPE = env("VANSAH_ATP_ASSET_TYPE", "folder")
+STP_KEY = env("VANSAH_STP_KEY")
+SPRINT_NAME = env("VANSAH_SPRINT_NAME")
+RELEASE_NAME = env("VANSAH_RELEASE_NAME")
+ENVIRONMENT_NAME = env("VANSAH_ENVIRONMENT_NAME")
+SCREENSHOT_PATH = env("VANSAH_SCREENSHOT_PATH")
+DEBUG = env("VANSAH_DEBUG").lower() in ("1", "true", "yes")
+
+# Skip the whole module when no token is configured.
+pytestmark = pytest.mark.skipif(
+ not VANSAH_TOKEN,
+ reason="VANSAH_TOKEN is not set in the environment/.env; skipping live Vansah tests.",
+)
+
+# A tiny valid 1x1 PNG, used when no real screenshot path is configured.
+_TINY_PNG = base64.b64decode(
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
+)
+
+
+def _screenshot_path(tmp_path):
+ if SCREENSHOT_PATH and os.path.isfile(SCREENSHOT_PATH):
+ return SCREENSHOT_PATH
+ p = tmp_path / "placeholder.png"
+ p.write_bytes(_TINY_PNG)
+ return str(p)
+
+
+@pytest.fixture
+def vansah():
+ """A configured VansahNode instance for each test."""
+ v = VansahNode()
+ v.set_vansah_url(VANSAH_URL)
+ v.set_vansah_token(VANSAH_TOKEN)
+ v.set_project_key(PROJECT_KEY)
+ v.set_jira_issue_key(JIRA_ISSUE_KEY)
+ v.set_test_folders_path(FOLDER_PATH)
+ if SPRINT_NAME:
+ v.set_sprint_name(SPRINT_NAME)
+ if RELEASE_NAME:
+ v.set_release_name(RELEASE_NAME)
+ if ENVIRONMENT_NAME:
+ v.set_environment_name(ENVIRONMENT_NAME)
+ v.set_advanced_test_plan_key(ATP_KEY)
+ v.set_standard_test_plan_key(STP_KEY)
+ v.set_debug(DEBUG)
+ return v
+
+
+# --------------------------------------------------------------------------- #
+# Pure-logic tests (do not require the network)
+# --------------------------------------------------------------------------- #
+
+
+def test_result_mapping():
+ v = VansahNode()
+ assert v.result_obj("passed") == {"id": 2}
+ assert v.result_obj("FAILED") == {"id": 1}
+ assert v.result_obj("na") == {"id": 0}
+ assert v.result_obj("untested") == {"id": 3}
+ assert v.result_obj(2) == {"id": 2}
+ assert v.result_obj("unknown") == {"id": 0}
+
+
+def test_folder_path_validation():
+ v = VansahNode()
+ assert v.is_valid_folder_path("regression/login/") is True
+ assert v.is_valid_folder_path("/leading") is False
+ assert v.is_valid_folder_path("nogap") is False
+ assert v.is_valid_folder_path("") is False
+
+
+def test_planned_run_and_plan_assets():
+ v = VansahNode()
+ v.set_test_plan_iteration(3)
+ assert v.planned_run_asset("KAN-P17") == {
+ "type": "plannedRun", "key": "KAN-P17", "iteration": 3,
+ }
+ v.set_test_folders_path("regression/login/")
+ v.TEST_PLAN_ASSET_TYPE = "folder"
+ assert v.test_plan_asset() == {"type": "folder", "folderPath": "regression/login/"}
+ v.set_jira_issue_key("KAN-1")
+ v.TEST_PLAN_ASSET_TYPE = "issue"
+ assert v.test_plan_asset() == {"type": "issue", "key": "KAN-1"}
+
+
+def test_iteration_out_of_range_ignored():
+ v = VansahNode()
+ v.set_test_plan_iteration(9) # invalid -> ignored
+ assert v.TEST_PLAN_ITERATION is None # stays default (API sends 1)
+ v.set_test_plan_iteration(4)
+ assert v.TEST_PLAN_ITERATION == 4
+
+
+# --------------------------------------------------------------------------- #
+# Live integration tests
+# --------------------------------------------------------------------------- #
+
+
+def test_run_from_jira_issue_step_by_step(vansah):
+ vansah.add_test_run_from_jira_issue(TEST_CASE_KEY)
+ assert vansah.TEST_RUN_IDENTIFIER, "Run identifier was not set"
+
+ for step in range(1, 3):
+ vansah.add_test_log("passed", f"Step {step} from the Python binding", step)
+ assert vansah.TEST_LOG_IDENTIFIER, f"Log identifier not set for step {step}"
+
+ # Update the most recent log, then remove it (placeholder is recreated).
+ vansah.update_test_log("failed", "Updated from the Python binding")
+ vansah.remove_test_log()
+ vansah.remove_test_run()
+
+
+def test_run_from_test_folder_with_screenshot(vansah, tmp_path, capsys):
+ vansah.add_test_run_from_test_folder(TEST_CASE_KEY)
+ assert vansah.TEST_RUN_IDENTIFIER, "Run identifier was not set"
+
+ shot = _screenshot_path(tmp_path)
+ vansah.add_test_log("passed", "Step 1 with screenshot", 1, image_path=shot)
+ assert vansah.TEST_LOG_IDENTIFIER, "Log identifier not set"
+
+ # The screenshot is uploaded via POST /logs//attachments; confirm it was accepted.
+ out = capsys.readouterr().out
+ assert f"Screenshot attached to LOG ID: {vansah.TEST_LOG_IDENTIFIER}" in out, \
+ "Attachment was not accepted by Vansah"
+
+ vansah.remove_test_run()
+
+
+def test_update_test_log_with_screenshot(vansah, tmp_path, capsys):
+ vansah.add_test_run_from_test_folder(TEST_CASE_KEY)
+ assert vansah.TEST_RUN_IDENTIFIER
+ vansah.add_test_log("passed", "Step 1", 1)
+ log_id = vansah.TEST_LOG_IDENTIFIER
+ assert log_id
+
+ shot = _screenshot_path(tmp_path)
+ vansah.update_test_log("failed", "Re-checked, attaching evidence", image_path=shot)
+ out = capsys.readouterr().out
+ assert f"Screenshot attached to LOG ID: {log_id}" in out, \
+ "Attachment on update was not accepted by Vansah"
+
+ vansah.remove_test_run()
+
+
+@pytest.mark.skipif(not STP_KEY, reason="VANSAH_STP_KEY not set")
+def test_run_from_standard_test_plan(vansah):
+ vansah.add_test_run_from_standard_test_plan(TEST_CASE_KEY)
+ assert vansah.TEST_RUN_IDENTIFIER, "STP run identifier was not set"
+ vansah.add_test_log("passed", "STP step 1 from the Python binding", 1)
+ assert vansah.TEST_LOG_IDENTIFIER
+ vansah.remove_test_run()
+
+
+@pytest.mark.skipif(not ATP_KEY, reason="VANSAH_ATP_KEY not set")
+def test_run_from_advanced_test_plan(vansah):
+ vansah.add_test_run_from_advanced_test_plan(ATP_ASSET_TYPE, TEST_CASE_KEY)
+ assert vansah.TEST_RUN_IDENTIFIER, "ATP run identifier was not set"
+ vansah.add_test_log("passed", "ATP step 1 from the Python binding", 1)
+ assert vansah.TEST_LOG_IDENTIFIER
+ vansah.remove_test_run()
+
+
+def test_quick_test_from_jira_issue(vansah):
+ vansah.add_quick_test_from_jira_issue(TEST_CASE_KEY, "passed")
+ assert vansah.TEST_RUN_IDENTIFIER, "Quick test run identifier was not set"
+ vansah.remove_test_run()
+
+
+def test_quick_test_from_test_folder(vansah):
+ vansah.add_quick_test_from_test_folder(TEST_CASE_KEY, 2)
+ assert vansah.TEST_RUN_IDENTIFIER, "Quick test run identifier was not set"
+ vansah.remove_test_run()
From abfa88f268edbb2193aa4bbef7e03fa1306de8d0 Mon Sep 17 00:00:00 2001
From: Shubham Mourya
Date: Fri, 17 Jul 2026 19:53:31 +0530
Subject: [PATCH 2/3] Update README to remove unnecessary details
Removed redundant line about iterating over test case steps.
---
README.md | 1 -
1 file changed, 1 deletion(-)
diff --git a/README.md b/README.md
index 7749a50..c28d8b8 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,6 @@
- Set a custom API URL and token for authentication.
- Easily connect your Python applications with `Vansah Test Management for Jira` to report test results and update test runs without manual intervention.
- Report results as a single overall verdict (Quick Test) or **step by step**, against a **Jira issue**, a **test folder**, a **Standard Test Plan**, or an **Advanced Test Plan**.
-- **Iterate** over every step of a test case — a run is created `Untested` so Vansah pre-creates one log per step, and each result updates the correct step log in place.
- Target a specific **Test Plan iteration** (1–5).
- Attach **screenshots** to test steps for more detailed reporting and analysis.
- Request-payload logging (`set_debug`) to troubleshoot integration issues; the token is sent as a header and is never printed.
From 2cb2db20cebb700a17590fb7fe42b421241a38e1 Mon Sep 17 00:00:00 2001
From: Shubham Mourya
Date: Fri, 17 Jul 2026 19:56:18 +0530
Subject: [PATCH 3/3] Update README to remove outdated sections
Removed sections on Documentation, Running the tests, and Contributing from the README.
---
README.md | 28 ----------------------------
1 file changed, 28 deletions(-)
diff --git a/README.md b/README.md
index c28d8b8..a0fce51 100644
--- a/README.md
+++ b/README.md
@@ -34,10 +34,7 @@
- [Folder Path, Advanced Test Plan (ATP), and Standard Test Plan (STP)](#folder-path-advanced-test-plan-atp-and-standard-test-plan-stp)
- [Methods Overview](#methods-overview)
- [Setter Methods of Vansah Binding](#setter-methods-of-vansah-binding)
-- [Documentation](#documentation)
-- [Running the tests](#running-the-tests)
- [Troubleshooting](#troubleshooting)
-- [Contributing](#contributing)
- [Developed By](#developed-by)
---
@@ -297,25 +294,6 @@ Enables or disables **request-payload logging**. When enabled, the JSON body sen
---
-## Documentation
-
-A step-by-step help article is available in [`docs/How-to-send-test-results-to-Vansah-from-Python.docx`](/docs/How-to-send-test-results-to-Vansah-from-Python.docx).
-
----
-
-## Running the tests
-
-[`test_vansah_node.py`](/test_vansah_node.py) contains a full integration suite. It is driven entirely by a local `.env` file (copy `.env` and fill in real values) and every network test is **skipped** (not failed) when `VANSAH_TOKEN` is blank, so it is safe to run without credentials.
-
-```bash
-pip install requests pytest
-pytest -v test_vansah_node.py
-```
-
-`.env` is already listed in `.gitignore` — never commit a real token.
-
----
-
## Troubleshooting
1. **`TEST_RUN_IDENTIFIER is not set` / "Please start a test run before recording a step result"**
@@ -334,12 +312,6 @@ Turn on `set_debug(True)` (or set `VANSAH_DEBUG=1`) to print each outgoing reque
---
-## Contributing
-
-We welcome contributions! Please feel free to submit issues or pull requests to improve this library.
-
----
-
## Developed By
[Vansah](https://vansah.com/)