thereportstack 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- thereportstack/__init__.py +0 -0
- thereportstack/api.py +258 -0
- thereportstack/cli.py +62 -0
- thereportstack/config.py +39 -0
- thereportstack/default_config.json +15 -0
- thereportstack-1.0.2.dist-info/METADATA +107 -0
- thereportstack-1.0.2.dist-info/RECORD +10 -0
- thereportstack-1.0.2.dist-info/WHEEL +5 -0
- thereportstack-1.0.2.dist-info/entry_points.txt +2 -0
- thereportstack-1.0.2.dist-info/top_level.txt +1 -0
|
File without changes
|
thereportstack/api.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
DEFAULTS = {
|
|
9
|
+
"typeOfReport": "pytest",
|
|
10
|
+
"testEnv": "Not Given",
|
|
11
|
+
"testType": "Not Given",
|
|
12
|
+
"tags": [],
|
|
13
|
+
"browser": "Not Given",
|
|
14
|
+
"browserVersion": "Not Given",
|
|
15
|
+
"iterationId": "Batch1",
|
|
16
|
+
"baseUrl": "http://localhost:4000/api/v1/public",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def normalize_tags(tags):
|
|
21
|
+
"""
|
|
22
|
+
Supports:
|
|
23
|
+
["smoke"]
|
|
24
|
+
["smoke,regression"]
|
|
25
|
+
["@smoke and @wallet"]
|
|
26
|
+
["@smoke or @wallet"]
|
|
27
|
+
"""
|
|
28
|
+
if not tags:
|
|
29
|
+
return []
|
|
30
|
+
|
|
31
|
+
normalized = []
|
|
32
|
+
|
|
33
|
+
for raw in tags:
|
|
34
|
+
raw = str(raw).lower()
|
|
35
|
+
raw = raw.replace("(", "").replace(")", "")
|
|
36
|
+
raw = re.sub(r"\s+(and|or)\s+", ",", raw)
|
|
37
|
+
|
|
38
|
+
parts = re.split(r"[,\s]+", raw)
|
|
39
|
+
|
|
40
|
+
for part in parts:
|
|
41
|
+
part = part.strip()
|
|
42
|
+
|
|
43
|
+
if not part:
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
if part.startswith("@"):
|
|
47
|
+
part = part[1:]
|
|
48
|
+
|
|
49
|
+
normalized.append(part)
|
|
50
|
+
|
|
51
|
+
# remove duplicates while keeping order
|
|
52
|
+
seen = set()
|
|
53
|
+
result = []
|
|
54
|
+
|
|
55
|
+
for item in normalized:
|
|
56
|
+
if item not in seen:
|
|
57
|
+
seen.add(item)
|
|
58
|
+
result.append(item)
|
|
59
|
+
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_auth_token(config):
|
|
64
|
+
# 1. token from config.json
|
|
65
|
+
token = config.get("token")
|
|
66
|
+
if token:
|
|
67
|
+
return token
|
|
68
|
+
|
|
69
|
+
# 2. token from env variable
|
|
70
|
+
env_token = os.getenv("DASHBOARD_TOKEN")
|
|
71
|
+
if env_token:
|
|
72
|
+
return env_token
|
|
73
|
+
|
|
74
|
+
# 3. optional login using email/password if you want later
|
|
75
|
+
email = config.get("email")
|
|
76
|
+
password = config.get("password")
|
|
77
|
+
|
|
78
|
+
if email and password:
|
|
79
|
+
base_url = config.get("baseUrl", DEFAULTS["baseUrl"])
|
|
80
|
+
login_url = f"{base_url}/auth/login"
|
|
81
|
+
|
|
82
|
+
response = requests.post(
|
|
83
|
+
login_url,
|
|
84
|
+
json={
|
|
85
|
+
"email": email,
|
|
86
|
+
"password": password,
|
|
87
|
+
},
|
|
88
|
+
timeout=20,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
response.raise_for_status()
|
|
92
|
+
|
|
93
|
+
data = response.json()
|
|
94
|
+
|
|
95
|
+
if "token" not in data:
|
|
96
|
+
raise RuntimeError("Login API did not return token")
|
|
97
|
+
|
|
98
|
+
return data["token"]
|
|
99
|
+
|
|
100
|
+
raise RuntimeError(
|
|
101
|
+
"No token found. Add 'token' in config.json or set DASHBOARD_TOKEN"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_endpoint(base_url, report_type, project_id):
|
|
106
|
+
mapping = {
|
|
107
|
+
"pytest": f"{base_url}/iterations/pyTest/{project_id}",
|
|
108
|
+
"cucumber": f"{base_url}/iterations/cucumber/{project_id}",
|
|
109
|
+
"newman": f"{base_url}/iterations/newman/{project_id}",
|
|
110
|
+
"normal": f"{base_url}/iterations/{project_id}",
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if report_type not in mapping:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Invalid typeOfReport '{report_type}'. "
|
|
116
|
+
f"Allowed: {', '.join(mapping.keys())}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return mapping[report_type]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def publish_report(config):
|
|
123
|
+
"""
|
|
124
|
+
Called from:
|
|
125
|
+
thereportstack start
|
|
126
|
+
Reads all values from thereportstack/config.json
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
report_type = config.get("typeOfReport", DEFAULTS["typeOfReport"])
|
|
130
|
+
test_env = config.get("testEnv", DEFAULTS["testEnv"])
|
|
131
|
+
test_type = config.get("testType", DEFAULTS["testType"])
|
|
132
|
+
browser = config.get("browser", DEFAULTS["browser"])
|
|
133
|
+
browser_version = config.get("browserVersion", DEFAULTS["browserVersion"])
|
|
134
|
+
iteration_id = config.get("iterationId", DEFAULTS["iterationId"])
|
|
135
|
+
tags = normalize_tags(config.get("tags", DEFAULTS["tags"]))
|
|
136
|
+
|
|
137
|
+
project_id = config.get("projectId")
|
|
138
|
+
report_file = config.get("file")
|
|
139
|
+
base_url = config.get("baseUrl", DEFAULTS["baseUrl"])
|
|
140
|
+
|
|
141
|
+
if not project_id:
|
|
142
|
+
raise RuntimeError("Missing 'projectId' in config.json")
|
|
143
|
+
|
|
144
|
+
if not report_file:
|
|
145
|
+
raise RuntimeError("Missing 'file' in config.json")
|
|
146
|
+
|
|
147
|
+
if not os.path.exists(report_file):
|
|
148
|
+
raise RuntimeError(f"Report file not found: {report_file}")
|
|
149
|
+
|
|
150
|
+
file_size_mb = os.path.getsize(report_file) / (1024 * 1024)
|
|
151
|
+
is_large_file = file_size_mb > 32
|
|
152
|
+
|
|
153
|
+
token = get_auth_token(config)
|
|
154
|
+
endpoint = get_endpoint(base_url, report_type, project_id)
|
|
155
|
+
|
|
156
|
+
payload = {
|
|
157
|
+
"iterationId": iteration_id,
|
|
158
|
+
"testType": test_type,
|
|
159
|
+
"testEnvironment": test_env,
|
|
160
|
+
"browserUsed": browser,
|
|
161
|
+
"browserVersion": browser_version,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Add tags based on upload method
|
|
165
|
+
if is_large_file:
|
|
166
|
+
payload["tags"] = tags
|
|
167
|
+
else:
|
|
168
|
+
payload["tags[]"] = tags
|
|
169
|
+
|
|
170
|
+
headers = {
|
|
171
|
+
"Authorization": f"Bearer {token}",
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
print("\nPublishing report...")
|
|
175
|
+
print(f"Type: {report_type}")
|
|
176
|
+
print(f"Project: {project_id}")
|
|
177
|
+
print(f"File: {report_file}")
|
|
178
|
+
print(f"Endpoint: {endpoint}")
|
|
179
|
+
|
|
180
|
+
if is_large_file:
|
|
181
|
+
print(f"\nLarge file detected ({file_size_mb:.2f}MB). Using Secure Large File Upload...")
|
|
182
|
+
|
|
183
|
+
print(" - Step 1: Getting secure upload link...")
|
|
184
|
+
init_url = f"{base_url}/iterations/upload/init"
|
|
185
|
+
init_resp = requests.post(
|
|
186
|
+
init_url,
|
|
187
|
+
json={"fileName": os.path.basename(report_file), "projectId": project_id},
|
|
188
|
+
headers=headers,
|
|
189
|
+
timeout=30
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if init_resp.status_code >= 300:
|
|
193
|
+
print("\nFailed to initialize large file upload")
|
|
194
|
+
print(f"Status Code: {init_resp.status_code}")
|
|
195
|
+
print(init_resp.text)
|
|
196
|
+
sys.exit(1)
|
|
197
|
+
|
|
198
|
+
init_data = init_resp.json()
|
|
199
|
+
upload_url = init_data.get("uploadUrl")
|
|
200
|
+
file_path_cloud = init_data.get("filePath")
|
|
201
|
+
|
|
202
|
+
if not upload_url or not file_path_cloud:
|
|
203
|
+
print("\nInvalid response from upload initialization (missing uploadUrl or filePath)")
|
|
204
|
+
sys.exit(1)
|
|
205
|
+
|
|
206
|
+
print(" - Step 2: Uploading file to cloud storage (this might take a while)...")
|
|
207
|
+
with open(report_file, "rb") as f:
|
|
208
|
+
put_resp = requests.put(upload_url, data=f, headers={"Content-Type": "application/json"}, timeout=600)
|
|
209
|
+
|
|
210
|
+
if put_resp.status_code >= 300:
|
|
211
|
+
print("\nFailed to upload file to cloud storage")
|
|
212
|
+
print(f"Status Code: {put_resp.status_code}")
|
|
213
|
+
print(put_resp.text)
|
|
214
|
+
sys.exit(1)
|
|
215
|
+
|
|
216
|
+
print(" - Step 3: Instructing server to process report...")
|
|
217
|
+
payload["uploadType"] = "cloud-storage"
|
|
218
|
+
payload["filePath"] = file_path_cloud
|
|
219
|
+
|
|
220
|
+
print("Payload:")
|
|
221
|
+
print(json.dumps(payload, indent=2))
|
|
222
|
+
|
|
223
|
+
response = requests.post(
|
|
224
|
+
endpoint,
|
|
225
|
+
headers=headers,
|
|
226
|
+
json=payload,
|
|
227
|
+
timeout=120,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
else:
|
|
231
|
+
print("Payload:")
|
|
232
|
+
print(json.dumps(payload, indent=2))
|
|
233
|
+
|
|
234
|
+
with open(report_file, "rb") as report:
|
|
235
|
+
files = {
|
|
236
|
+
"files": (
|
|
237
|
+
os.path.basename(report_file),
|
|
238
|
+
report,
|
|
239
|
+
"application/json",
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
response = requests.post(
|
|
244
|
+
endpoint,
|
|
245
|
+
headers=headers,
|
|
246
|
+
data=payload,
|
|
247
|
+
files=files,
|
|
248
|
+
timeout=60,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if response.status_code >= 300:
|
|
252
|
+
print("\n Upload failed")
|
|
253
|
+
print(f"Status Code: {response.status_code}")
|
|
254
|
+
print(response.text)
|
|
255
|
+
sys.exit(1)
|
|
256
|
+
|
|
257
|
+
print("\nReport uploaded successfully")
|
|
258
|
+
print(response.text)
|
thereportstack/cli.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# src/thereportstack/cli.py
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from .config import ensure_config_exists, load_config
|
|
5
|
+
from .api import publish_report
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main():
|
|
9
|
+
parser = argparse.ArgumentParser(prog="thereportstack")
|
|
10
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
11
|
+
|
|
12
|
+
init_parser = subparsers.add_parser(
|
|
13
|
+
"init",
|
|
14
|
+
help="Create or update config.json"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
init_parser.add_argument("--typeOfReport")
|
|
18
|
+
init_parser.add_argument("--projectId")
|
|
19
|
+
init_parser.add_argument("--file")
|
|
20
|
+
init_parser.add_argument("--testEnv")
|
|
21
|
+
init_parser.add_argument("--testType")
|
|
22
|
+
init_parser.add_argument("--tags", nargs="*")
|
|
23
|
+
init_parser.add_argument("--browser")
|
|
24
|
+
init_parser.add_argument("--browserVersion")
|
|
25
|
+
init_parser.add_argument("--iterationId")
|
|
26
|
+
init_parser.add_argument("--token")
|
|
27
|
+
init_parser.add_argument("--email")
|
|
28
|
+
init_parser.add_argument("--password")
|
|
29
|
+
init_parser.add_argument("--baseUrl")
|
|
30
|
+
|
|
31
|
+
subparsers.add_parser(
|
|
32
|
+
"start",
|
|
33
|
+
help="Start automation using config.json"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
args = parser.parse_args()
|
|
37
|
+
|
|
38
|
+
if args.command == "init":
|
|
39
|
+
config_file = ensure_config_exists(
|
|
40
|
+
typeOfReport=args.typeOfReport,
|
|
41
|
+
projectId=args.projectId,
|
|
42
|
+
file=args.file,
|
|
43
|
+
testEnv=args.testEnv,
|
|
44
|
+
testType=args.testType,
|
|
45
|
+
tags=args.tags,
|
|
46
|
+
browser=args.browser,
|
|
47
|
+
browserVersion=args.browserVersion,
|
|
48
|
+
iterationId=args.iterationId,
|
|
49
|
+
token=args.token,
|
|
50
|
+
email=args.email,
|
|
51
|
+
password=args.password,
|
|
52
|
+
baseUrl=args.baseUrl,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
print(f"Config updated: {config_file}")
|
|
56
|
+
|
|
57
|
+
elif args.command == "start":
|
|
58
|
+
config = load_config()
|
|
59
|
+
publish_report(config)
|
|
60
|
+
|
|
61
|
+
else:
|
|
62
|
+
parser.print_help()
|
thereportstack/config.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# src/thereportstack/config.py
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import json
|
|
5
|
+
import importlib.resources as resources
|
|
6
|
+
|
|
7
|
+
APP_DIR = Path.cwd() / "thereportstack"
|
|
8
|
+
CONFIG_FILE = APP_DIR / "config.json"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def ensure_config_exists(**kwargs):
|
|
12
|
+
APP_DIR.mkdir(exist_ok=True)
|
|
13
|
+
|
|
14
|
+
if CONFIG_FILE.exists():
|
|
15
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
16
|
+
config = json.load(f)
|
|
17
|
+
else:
|
|
18
|
+
with resources.files("thereportstack").joinpath(
|
|
19
|
+
"default_config.json"
|
|
20
|
+
).open("r", encoding="utf-8") as f:
|
|
21
|
+
config = json.load(f)
|
|
22
|
+
|
|
23
|
+
# Update only fields provided by user
|
|
24
|
+
for key, value in kwargs.items():
|
|
25
|
+
if value is not None:
|
|
26
|
+
config[key] = value
|
|
27
|
+
|
|
28
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
29
|
+
json.dump(config, f, indent=2)
|
|
30
|
+
|
|
31
|
+
return CONFIG_FILE
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def load_config():
|
|
35
|
+
if not CONFIG_FILE.exists():
|
|
36
|
+
ensure_config_exists()
|
|
37
|
+
|
|
38
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
39
|
+
return json.load(f)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"typeOfReport": "pytest",
|
|
3
|
+
"projectId": "extract the projectId from TheReportStack team",
|
|
4
|
+
"file": "Path of json file",
|
|
5
|
+
"testEnv": "",
|
|
6
|
+
"testType": "",
|
|
7
|
+
"tags": ["smoke"],
|
|
8
|
+
"browser": "",
|
|
9
|
+
"browserVersion": "",
|
|
10
|
+
"iterationId": "any unique string if you like to give",
|
|
11
|
+
"token": "",
|
|
12
|
+
"email": "",
|
|
13
|
+
"password": "",
|
|
14
|
+
"baseUrl": "https://app.thereportstack.com/api/v1/public"
|
|
15
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: thereportstack
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Automation dashboard report uploader
|
|
5
|
+
Author-email: Your Name <you@example.com>
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: requests>=2.31.0
|
|
9
|
+
|
|
10
|
+
# TheReportStack CLI
|
|
11
|
+
<br>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<b>A powerful CLI and Python package to seamlessly upload your automated test execution reports to the TheReportStack Dashboard.</b>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## ⚡ Features
|
|
20
|
+
|
|
21
|
+
- **Upload Instantly:** Upload PyTest, Cucumber, and Newman JSON reports directly to your TheReportStack Dashboard project.
|
|
22
|
+
- **Smart Tags:** Supports parsing complex tag expressions (e.g. `["@smoke and @wallet"]`).
|
|
23
|
+
- **Flexible Authentication:** Authenticate via project tokens, Environment Variables, or email & password.
|
|
24
|
+
- **Zero Coding Required:** Configure once with a simple json file and trigger from anywhere in your CI/CD pipelines.
|
|
25
|
+
|
|
26
|
+
## 📦 Installation
|
|
27
|
+
|
|
28
|
+
Install the package via `pip`:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install thereportstack
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 🚀 Quick Start
|
|
35
|
+
|
|
36
|
+
### 1. Initialize Configuration
|
|
37
|
+
Navigate to your project directory and run the initialization command. This creates the configuration structure.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
thereportstack init
|
|
41
|
+
```
|
|
42
|
+
This generates a `thereportstack/config.json` file in your current directory with default values.
|
|
43
|
+
|
|
44
|
+
You can also configure the project directly from the command line by passing arguments. This will instantly populate or update the `config.json` file:
|
|
45
|
+
```bash
|
|
46
|
+
thereportstack init --projectId YOUR_PROJECT_ID --tags "smoke" "regression" --typeOfReport pytest
|
|
47
|
+
```
|
|
48
|
+
### 2. Configure Your Project
|
|
49
|
+
Edit the generated `thereportstack/config.json` with your project's specifics.
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"typeOfReport": "pytest",
|
|
54
|
+
"projectId": "YOUR_PROJECT_ID",
|
|
55
|
+
"file": "path/to/report.json",
|
|
56
|
+
"testEnv": "QA",
|
|
57
|
+
"testType": "WEB",
|
|
58
|
+
"tags": ["smoke", "regression"],
|
|
59
|
+
"browser": "chrome",
|
|
60
|
+
"browserVersion": "latest",
|
|
61
|
+
"iterationId": "Batch1",
|
|
62
|
+
"baseUrl": "https://app.thereportstack.com/api/v1/public"
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
*Don't want to expose your token in code?* Remove the `"token"` field from the config and export it as an environment variable in your CI/CD tool:
|
|
67
|
+
```bash
|
|
68
|
+
export DASHBOARD_TOKEN="YOUR_API_TOKEN"
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 3. Upload Report
|
|
72
|
+
Once your config is set and your testing framework has generated the JSON report, run:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
thereportstack start
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
That's it! 🚀 Your test execution report is now beautifully cataloged on the TheReportStack Dashboard.
|
|
79
|
+
|
|
80
|
+
## 🔧 Configuration Reference
|
|
81
|
+
|
|
82
|
+
| Parameter | Description | Default Value |
|
|
83
|
+
|------------------|-------------------------------------------------------------------------------------------------|------------------------|
|
|
84
|
+
| `typeOfReport` | The framework generating the report (`pytest`, `cucumber`, `newman`, `normal`) | `pytest` |
|
|
85
|
+
| `projectId` | Your unique project ID from the TheReportStack dashboard | `""` |
|
|
86
|
+
| `file` | Relative or absolute path to the generated JSON test report | `report.json` |
|
|
87
|
+
| `testEnv` | Environment where the test triggered (`QA`, `Staging`, `Prod`, etc.) | `QA` |
|
|
88
|
+
| `testType` | Type of Testing (`WEB`, `API`, `MOBILE`) | `WEB` |
|
|
89
|
+
| `tags` | Array of strings representing your test suites/scenario tags (`["smoke", "regression"]`) | `["smoke"]` |
|
|
90
|
+
| `browser` | Name of the executed browser (`chrome`, `firefox`, `safari`, `edge`) | `chrome` |
|
|
91
|
+
| `browserVersion` | Version of the browser | `latest` |
|
|
92
|
+
| `token` | Personal access token. Overridden by the `DASHBOARD_TOKEN` environment variable if present. | `""` |
|
|
93
|
+
|
|
94
|
+
## 🔑 Authentication Methods
|
|
95
|
+
TheReportStack gives you several options for secure authentication:
|
|
96
|
+
1. **Config Token**: Place `"token": "YOUR_TOKEN"` directly in the `config.json`.
|
|
97
|
+
2. **Environment Variable**: Export `DASHBOARD_TOKEN` on your machine or CI/CD runner.
|
|
98
|
+
3. **Email & Password**: You can provide `"email"` and `"password"` fields in `config.json` and the CLI will automatically securely negotiate an authentication token. (Token/Environment Token takes precedent).
|
|
99
|
+
|
|
100
|
+
## ✨ Tag Parsing Support
|
|
101
|
+
To make QA tagging flexible, `thereportstack` CLI automatically normalizes boolean-style or nested BDD tags coming from your project definitions before sending them to the dashboard:
|
|
102
|
+
- `["@smoke and @wallet"]` ➡️ `["smoke", "wallet"]`
|
|
103
|
+
- `["smoke, regression"]` ➡️ `["smoke", "regression"]`
|
|
104
|
+
- `["(checkout)"]` ➡️ `["checkout"]`
|
|
105
|
+
|
|
106
|
+
## 🤝 Support
|
|
107
|
+
If you encounter any issues integrating this into your Python, Node, Java, or Ruby pipelines, please reach out directly to the TheReportStack maintainers.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
thereportstack/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
thereportstack/api.py,sha256=Ai4zEP0PCbApxXkJLqs1XM_6cTQ96eriZXyVRzKBLdo,7248
|
|
3
|
+
thereportstack/cli.py,sha256=pR4UFOSa5V3k9Vp4Apqm1mDVkmT_SWHF2n_RV6mW45Y,1801
|
|
4
|
+
thereportstack/config.py,sha256=cZEjDj2748MW2r6WgUw_tBheNbTJbncuqE3UGOYcovQ,994
|
|
5
|
+
thereportstack/default_config.json,sha256=N5OMboXEfIR7xQvMXfNco_VJqj_iNBIFg-EuTvzhbCI,391
|
|
6
|
+
thereportstack-1.0.2.dist-info/METADATA,sha256=ryxPczxEIvhuYyUIv5itUeWIAHMF4Zh8FIIa_a4j-h8,5041
|
|
7
|
+
thereportstack-1.0.2.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
8
|
+
thereportstack-1.0.2.dist-info/entry_points.txt,sha256=G_YwucqV_1AjdOrSVZfB08HJzCQlgfnb5WS2zRNBoRA,59
|
|
9
|
+
thereportstack-1.0.2.dist-info/top_level.txt,sha256=laVLe1Tkt4P2WYW8JMJfc5p5Ok-dwBlYEQ4be3fNCWY,15
|
|
10
|
+
thereportstack-1.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
thereportstack
|