Skip to content

Commit 4c94d2d

Browse files
author
Shriyansh Agnihotri
committed
Rearchitecting the prompts and agents base design, releasing api and security testing agent.
Rearchitecting the prompts and agents base design, releasing api and security testing agent. 1. The release includes making subagents for Hercules for dealing with different type of tasks. 2. Improving the prompts for Hercules and other sub-agents. 3. Introducing API testing agent. 4. Introducing Security testing agent. addressing bulk input in browser agent, addressing backspace clear in browser agent during entertext activity
1 parent 1f16781 commit 4c94d2d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+4222
-650
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ testzeus-hercules/**/*.feature
156156
**/proofs
157157
**/tests/run_data
158158
**/agents_llm_config.json
159+
**/helper_scripts/output
159160

160161

161162
**/installation_id.txt

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ test: lint ## Run tests and generate coverage report.
4141
.PHONY: test-case ## Run selective test case.
4242
test-case: lint ## Run a specific test case.
4343
@read -p "Enter the test case (e.g., multilingual): " TEST_CASE && \
44-
poetry run pytest -v --pdb tests/test_feature_execution.py::test_feature_execution[$$TEST_CASE]
44+
poetry run pytest -v tests/test_feature_execution.py::test_feature_execution[$$TEST_CASE]
4545

4646
.PHONY: watch
4747
watch: ## Run tests on every change.
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
#!/usr/bin/env python
2+
3+
# how to run
4+
# python helper_scripts/generate_api_functional_gherkin_test.py tests/test_features/ApiTesting/api_spec.yml --output=helper_scripts/output --model=gpt-4o --number_of_testcase=50
5+
6+
import os
7+
import sys
8+
import argparse
9+
from openai import OpenAI
10+
from typing import List
11+
12+
13+
def read_openapi_spec(file_path: str) -> str:
14+
"""
15+
Reads the OpenAPI specification from a file.
16+
17+
Args:
18+
file_path (str): The path to the OpenAPI spec file.
19+
20+
Returns:
21+
str: The content of the OpenAPI spec file.
22+
"""
23+
with open(file_path, "r", encoding="utf-8") as f:
24+
content = f.read()
25+
return content
26+
27+
28+
def prepare_prompt(p2: str, openapi_spec: str) -> str:
29+
"""
30+
Prepares the prompt for the OpenAI API.
31+
32+
Args:
33+
p2 (str): The initial part of the prompt.
34+
openapi_spec (str): The OpenAPI specification.
35+
36+
Returns:
37+
str: The complete prompt.
38+
"""
39+
prompt = f"{p2}\n\nOpenAPI Specification:\n{openapi_spec}"
40+
return prompt
41+
42+
43+
def generate_test_cases(prompt: str, model: str) -> str:
44+
"""
45+
Generates test cases using the OpenAI API.
46+
47+
Args:
48+
prompt (str): The prompt to send to the OpenAI API.
49+
model (str): The model to use for the OpenAI API.
50+
51+
Returns:
52+
str: The generated test cases.
53+
"""
54+
client = OpenAI()
55+
if "o1" in model:
56+
completion = client.chat.completions.create(
57+
model=model,
58+
messages=[{"role": "user", "content": prompt}],
59+
)
60+
else:
61+
completion = client.chat.completions.create(
62+
model=model,
63+
messages=[{"role": "user", "content": prompt}],
64+
temperature=0.7,
65+
)
66+
67+
response = completion.choices[0].message.content
68+
print(f"Response from OpenAI API: {response}")
69+
return response
70+
71+
72+
def ensure_output_folder(output_folder: str) -> None:
73+
"""
74+
Ensures that the output folder exists.
75+
76+
Args:
77+
output_folder (str): The path to the output folder.
78+
"""
79+
if not os.path.exists(output_folder):
80+
os.makedirs(output_folder)
81+
82+
83+
def split_features(response_text: str) -> List[str]:
84+
"""
85+
Splits the response text into individual features.
86+
87+
Args:
88+
response_text (str): The response text containing multiple features.
89+
90+
Returns:
91+
List[str]: A list of individual features.
92+
"""
93+
features = []
94+
current_feature = ""
95+
lines = response_text.split("\n")
96+
for line in lines:
97+
if line.strip().startswith("Feature:"):
98+
if current_feature:
99+
features.append(current_feature.strip())
100+
current_feature = ""
101+
current_feature += line + "\n"
102+
if current_feature.strip():
103+
features.append(current_feature.strip())
104+
return features
105+
106+
107+
def get_base_name(file_path: str) -> str:
108+
"""
109+
Gets the base name of a file without the extension.
110+
111+
Args:
112+
file_path (str): The path to the file.
113+
114+
Returns:
115+
str: The base name of the file.
116+
"""
117+
base_name = os.path.splitext(os.path.basename(file_path))[0]
118+
return base_name
119+
120+
121+
def create_output_subfolder(output_folder: str, base_name: str) -> str:
122+
"""
123+
Creates a subfolder in the output folder.
124+
125+
Args:
126+
output_folder (str): The path to the output folder.
127+
base_name (str): The base name for the subfolder.
128+
129+
Returns:
130+
str: The path to the created subfolder.
131+
"""
132+
subfolder_path = os.path.join(output_folder, base_name)
133+
if not os.path.exists(subfolder_path):
134+
os.makedirs(subfolder_path)
135+
return subfolder_path
136+
137+
138+
def write_feature_files(features: List[str], subfolder_path: str) -> None:
139+
"""
140+
Writes the feature files to the subfolder.
141+
142+
Args:
143+
features (List[str]): A list of features to write.
144+
subfolder_path (str): The path to the subfolder.
145+
"""
146+
for idx, feature in enumerate(features):
147+
# Extract the feature name if possible
148+
first_line = feature.split("\n")[0]
149+
if first_line.startswith("Feature:"):
150+
feature_name = first_line[len("Feature:") :].strip().replace(" ", "_")
151+
file_name = f"{feature_name}.feature"
152+
else:
153+
file_name = f"feature_{idx+1}.feature"
154+
file_path = os.path.join(subfolder_path, file_name)
155+
with open(file_path, "w", encoding="utf-8") as f:
156+
feature = feature.replace("```", "").replace("gherkin", "")
157+
f.write(feature)
158+
159+
160+
def main() -> None:
161+
"""
162+
The main function to generate Gherkin test cases from OpenAPI spec files.
163+
"""
164+
p2 = """Analyse (thoroughly examine and break down) the given API specification to produce detailed Gherkin test cases following the provided testing plan. Focus on creating positive, negative, and business-as-usual scenarios. Ensure your test cases validate data correctness, data types, null value handling, and adherence to the API specification. Cover all testing areas: functional, postive, negative, error handling, and integration. Be extremely direct, clear, DETAILED and corrective in your approach. Your final output should be the strict detailed Gherkin test cases, ready for execution. generate a big spread of testcases at least {number_of_testcase}. for each combination of datatypes, fields and null values generate the testcases
165+
NEVER PUT ### in the testcases. Always write the testcases in the Gherkin format. follow the example to generate output
166+
ONLY RETURN THE GHERKIN FILES, NO HEADING OR EXPLINATION NEEDED.
167+
example output:
168+
169+
Feature: feature details 1
170+
Scenario: Scenario_details
171+
Given ...
172+
When ...
173+
Then ...
174+
Scenario: Scenario_details
175+
Given ...
176+
When ...
177+
Then ...
178+
Feature: feature details 2
179+
Scenario: Scenario_details
180+
Given ...
181+
When ...
182+
Then ...
183+
Feature: feature details 3
184+
Scenario: Scenario_details
185+
Given ...
186+
When ...
187+
And ...
188+
Then ...
189+
And ...
190+
"""
191+
192+
parser = argparse.ArgumentParser(
193+
description="Generate Gherkin test cases from OpenAPI spec files."
194+
)
195+
parser.add_argument(
196+
"input_files",
197+
metavar="input_files",
198+
type=str,
199+
nargs="+",
200+
help="One or more OpenAPI spec files (YAML or JSON).",
201+
)
202+
parser.add_argument(
203+
"--output",
204+
metavar="output",
205+
type=str,
206+
required=True,
207+
help="Output folder path where feature files will be generated.",
208+
)
209+
parser.add_argument(
210+
"--model",
211+
metavar="model",
212+
type=str,
213+
default="o1-preview",
214+
help="The model to use for the OpenAI API (default: o1-preview).",
215+
)
216+
parser.add_argument(
217+
"--number_of_testcase",
218+
metavar="number_of_testcase",
219+
type=int,
220+
default=100,
221+
help="The number of test cases to generate (default: 100).",
222+
)
223+
args = parser.parse_args()
224+
225+
api_key = os.getenv("OPENAI_API_KEY")
226+
if not api_key:
227+
print("Error: OPENAI_API_KEY environment variable not set.")
228+
sys.exit(1)
229+
230+
ensure_output_folder(args.output)
231+
232+
for file_path in args.input_files:
233+
print(f"Processing file: {file_path}")
234+
openapi_spec = read_openapi_spec(file_path)
235+
p2 = p2.replace("{number_of_testcase}", str(args.number_of_testcase))
236+
prompt = prepare_prompt(p2, openapi_spec)
237+
try:
238+
test_cases = generate_test_cases(prompt, args.model)
239+
except Exception as e:
240+
print(f"Error generating test cases for {file_path}: {e}")
241+
continue
242+
features = split_features(test_cases)
243+
base_name = get_base_name(file_path)
244+
subfolder_path = create_output_subfolder(args.output, base_name)
245+
write_feature_files(features, subfolder_path)
246+
print(f"Generated {len(features)} feature files in {subfolder_path}")
247+
248+
249+
if __name__ == "__main__":
250+
main()

0 commit comments

Comments
 (0)