Skip to content

Commit 9f65c8f

Browse files
authored
Merge pull request #45 from codebeaver-ai/rc
Release candidate 0.1.3
2 parents db0fb75 + fa09a6f commit 9f65c8f

File tree

16 files changed

+996
-59
lines changed

16 files changed

+996
-59
lines changed

.coveragerc

Lines changed: 0 additions & 17 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## [0.1.2] - 2025-03-26
2+
3+
### Added
4+
5+
- Added a new command `codebeaver e2e` to run end-to-end tests defined in configuration
6+
- Added XML report generation for end-to-end tests
7+
18
## [0.1.0] - 2024-03-10
29

310
### Added

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,11 @@ CodeBeaver currently supports:
166166

167167
- [✅] Unit tests
168168
- [✅] E2E Tests
169+
- [✅] Add support for more models (thank you [VinciGit00](https://github.com/VinciGit00)!)
169170
- [ ] Better reporting
170171
- [ ] Integration Tests
171172
- [ ] Unit Tests: Add support for more languages and frameworks
172173
- [ ] Unit Tests: Add support for more testing frameworks
173-
- [ ] Add support for more models
174174

175175
## Let's chat!
176176

coverage.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/codebeaver/AnalyzeError.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from codebeaver.types import TestErrorType
2-
import openai
2+
import os
33
from pathlib import Path
44
import logging
5+
from .models.provider_factory import ProviderFactory, ProviderType
56

67
logger = logging.getLogger("codebeaver")
78

@@ -40,6 +41,8 @@ def __init__(
4041
---ERROR LOG---
4142
{error}
4243
"""
44+
provider_type = os.getenv("CODEBEAVER_PROVIDER", "openai")
45+
self.provider = ProviderFactory.get_provider(ProviderType(provider_type))
4346

4447
def analyze(self) -> tuple[TestErrorType, str | None]:
4548
"""
@@ -51,8 +54,7 @@ def analyze(self) -> tuple[TestErrorType, str | None]:
5154
if self.error == "exit status 1":
5255
return TestErrorType.TEST, self.error
5356

54-
response = openai.chat.completions.create(
55-
model="o3-mini",
57+
response = self.provider.create_chat_completion(
5658
messages=[
5759
{
5860
"role": "user",

src/codebeaver/E2E.py

Lines changed: 20 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,19 @@
55
from browser_use.browser.context import BrowserContext
66
from dotenv import load_dotenv
77
from langchain_openai import ChatOpenAI
8-
from pydantic import BaseModel
8+
99
from .GitUtils import GitUtils
1010
import logging
1111
from browser_use.browser.context import BrowserContextConfig
1212
from pathlib import Path
13+
from .types import End2endTest, TestCase
14+
from .Report import Report
1315

1416
load_dotenv()
1517

1618
logger = logging.getLogger("codebeaver")
1719

1820

19-
class End2endTest(BaseModel):
20-
steps: list[str]
21-
url: str
22-
passed: bool = False
23-
errored: bool = False
24-
comment: str = ""
25-
name: str
26-
27-
def __init__(self, name: str, steps: list[str], url: str):
28-
super().__init__(name=name, steps=steps, url=url)
29-
30-
31-
class TestCase(BaseModel):
32-
passed: bool
33-
comment: str
34-
35-
3621
controller = Controller(output_model=TestCase)
3722

3823

@@ -61,13 +46,20 @@ async def run(self) -> list[End2endTest]:
6146
url=test["url"],
6247
)
6348
test_result = await self.run_test(test)
64-
all_tests.append(test_result)
49+
test.passed = not test_result.failure
50+
test.errored = test_result.errored
51+
test.comment = test_result.comment
52+
all_tests.append(test)
6553
# write the results to e2e.json. this is temporary, we will eventually use the report class
6654
with open(Path.cwd() / ".codebeaver/e2e.json", "w") as f:
6755
json.dump([test.model_dump() for test in all_tests], f)
56+
report = Report()
57+
report.add_e2e_results(all_tests)
58+
with open(Path.cwd() / ".codebeaver/e2e.xml", "w") as f:
59+
f.write(report.generate_xml_report())
6860
return all_tests
6961

70-
async def run_test(self, test: End2endTest) -> End2endTest:
62+
async def run_test(self, test: End2endTest) -> TestCase:
7163
GitUtils.ensure_codebeaver_folder_exists_and_in_gitignore() # avoid committing logs, screenshots and so on
7264
config_context = BrowserContextConfig(
7365
save_recording_path=Path.cwd() / ".codebeaver/",
@@ -81,24 +73,22 @@ async def run_test(self, test: End2endTest) -> End2endTest:
8173
)
8274
context = BrowserContext(browser=browser, config=config_context)
8375
agent = Agent(
84-
task=f"""You are a QA tester. Follow these steps:
76+
task=f"""You are a QA tester. Follow these instructions to perform the test called {test.name}:
8577
* Go to {test.url}
8678
"""
87-
+ "\n".join(f"* {step}" for step in test.steps),
79+
+ "\n".join(f"* {step}" for step in test.steps)
80+
+ "\n\nIf any step that starts with 'Check' fails, the result is a failure",
8881
llm=ChatOpenAI(model="gpt-4o"),
89-
# browser=browser,
9082
controller=controller,
9183
browser_context=context,
9284
)
9385
history = await agent.run()
9486
await context.close()
9587
result = history.final_result()
9688
if result:
97-
parsed: TestCase = TestCase.model_validate_json(result)
98-
test.passed = parsed.passed
99-
test.comment = parsed.comment
100-
return test
89+
test_result: TestCase = TestCase.model_validate_json(result)
90+
return test_result
10191
else:
102-
test.errored = True
103-
test.comment = "No result from the test"
104-
return test
92+
test_result.errored = True
93+
test_result.comment = "No result from the test"
94+
return test_result

src/codebeaver/Report.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from .types import End2endTest
2+
3+
4+
class Report:
5+
"""
6+
A class that generates a report of the test results. For now, only used for E2E test results, but in the future it will produce one unique report if it will be the case.
7+
"""
8+
9+
def __init__(self) -> None:
10+
self.e2e_results: list[End2endTest] = []
11+
12+
def add_e2e_results(self, e2e_results: list[End2endTest]) -> None:
13+
self.e2e_results.extend(e2e_results)
14+
15+
def generate_xml_report(self) -> str:
16+
"""
17+
Generate a XML report of the test results, in a format that is compatible with junit.xml
18+
"""
19+
xml_lines = ['<?xml version="1.0" encoding="UTF-8"?>']
20+
xml_lines.append('<testsuites name="End2End Tests">')
21+
22+
# Create a single test suite for all E2E tests
23+
xml_lines.append(
24+
' <testsuite name="E2E Test Suite" tests="{}"'.format(
25+
len(self.e2e_results)
26+
)
27+
)
28+
29+
# Count failures and errors
30+
failures = sum(
31+
1 for test in self.e2e_results if not test.passed and not test.errored
32+
)
33+
errors = sum(1 for test in self.e2e_results if test.errored)
34+
xml_lines.append(f' failures="{failures}" errors="{errors}">')
35+
36+
# Add individual test cases
37+
for test in self.e2e_results:
38+
xml_lines.append(
39+
' <testcase name="{}" classname="E2ETest">'.format(test.name)
40+
)
41+
42+
# Add steps as system-out
43+
steps_text = "\n".join(test.steps)
44+
xml_lines.append(f" <system-out>{steps_text}</system-out>")
45+
46+
# Add failure or error information if present
47+
if test.errored:
48+
xml_lines.append(
49+
' <error message="Test execution error" type="Error">'
50+
)
51+
xml_lines.append(f" {test.comment}")
52+
xml_lines.append(" </error>")
53+
elif not test.passed:
54+
xml_lines.append(' <failure message="Test failed" type="Failure">')
55+
xml_lines.append(f" {test.comment}")
56+
xml_lines.append(" </failure>")
57+
58+
xml_lines.append(" </testcase>")
59+
60+
xml_lines.append(" </testsuite>")
61+
xml_lines.append("</testsuites>")
62+
63+
return "\n".join(xml_lines)
64+
65+
def generate_html_report(self) -> str:
66+
raise NotImplementedError("HTML report generation not implemented")
67+
68+
def generate_json_report(self) -> str:
69+
raise NotImplementedError("JSON report generation not implemented")

src/codebeaver/ResponseParser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
class ResponseParser:
44
@staticmethod
55
def parse(response: str) -> str:
6-
test_regex_match = re.findall(r"```test\n(.*?)```", response, re.DOTALL)
6+
test_regex_match = re.findall(r"<test>\s*\[test\](.*?)</test>", response, re.DOTALL)
77
test_content = test_regex_match[0] if test_regex_match else ""
8-
test_content = test_content.replace("```test", "").replace("``test", "")
8+
test_content = test_content.strip()
99
return test_content

0 commit comments

Comments
 (0)