botrytis 0.1.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.claude/settings.local.json +19 -0
- data/CLAUDE.md +85 -0
- data/README.md +258 -1
- data/debug_step_args.rb +6 -0
- data/future_tests.md +141 -0
- data/lib/botrytis/cucumber.rb +151 -0
- data/lib/botrytis/formatter.rb +75 -0
- data/lib/botrytis/semantic_match_generator.rb +39 -1
- data/lib/botrytis/semantic_matcher.rb +261 -0
- data/lib/botrytis/version.rb +1 -1
- metadata +8 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fdc6ddc981fa9cd6507c3534d0849cc7c2e773cadd5360e3daed1b91c4b28039
|
4
|
+
data.tar.gz: 2010aea7e2644b3e6c0cc43abd6ef73cf2f92165af64270d9085d285f539abfe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98ab98ee673362e2d352a1dd2f6b6ac3913611204a1e156b17915541d2873e0a8d1cdc310879f39e50aaa026d3fc9b4f1b629e0c0606fae555738cbfaac0b2e8
|
7
|
+
data.tar.gz: bffbf314f6a14f2bb398ed4a52c5d716dc849d5229c69e580133dd25581d661bbf5765859ec7f54b3e0edb3e38ff6d5d3695fb7dd0403e65a578860dd99c8982
|
@@ -0,0 +1,19 @@
|
|
1
|
+
{
|
2
|
+
"permissions": {
|
3
|
+
"allow": [
|
4
|
+
"Bash(bundle exec rake:*)",
|
5
|
+
"Bash(bundle exec rspec:*)",
|
6
|
+
"Bash(bundle exec irb:*)",
|
7
|
+
"Bash(bundle exec cucumber:*)",
|
8
|
+
"Bash(bundle exec ruby:*)",
|
9
|
+
"Bash(mkdir:*)",
|
10
|
+
"Bash(cp:*)",
|
11
|
+
"Bash(touch:*)",
|
12
|
+
"Bash(BOTRYTIS_LIVE_API=true bundle exec cucumber --name \"Authentication variations\")",
|
13
|
+
"Bash(bundle exec rails generate model:*)",
|
14
|
+
"Bash(bundle exec rails generate:*)",
|
15
|
+
"Bash(ls:*)"
|
16
|
+
],
|
17
|
+
"deny": []
|
18
|
+
}
|
19
|
+
}
|
data/CLAUDE.md
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# CLAUDE.md
|
2
|
+
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
4
|
+
|
5
|
+
## Project Overview
|
6
|
+
|
7
|
+
Botrytis is a Ruby gem that provides LLM-powered semantic matching for Cucumber step definitions. It enables fuzzy matching of Cucumber steps using large language models, making BDD tests more flexible by matching similar but not exact step text.
|
8
|
+
|
9
|
+
## Architecture
|
10
|
+
|
11
|
+
### Core Components
|
12
|
+
|
13
|
+
- **SemanticMatcher** (`lib/botrytis/semantic_matcher.rb`): Main matching engine that finds semantic matches between step text and available step definitions using LLMs
|
14
|
+
- **SemanticMatchGenerator** (`lib/botrytis/semantic_match_generator.rb`): LLM interaction layer built on Sublayer for generating semantic matches
|
15
|
+
- **Configuration** (`lib/botrytis/configuration.rb`): Configurable settings for LLM provider, model, confidence thresholds, and caching
|
16
|
+
- **Formatter** (`lib/botrytis/formatter.rb`): Custom Cucumber formatter integration
|
17
|
+
|
18
|
+
### Key Dependencies
|
19
|
+
|
20
|
+
- **cucumber**: Core Cucumber framework (>= 9)
|
21
|
+
- **sublayer**: LLM abstraction layer (>= 0.2.8) for AI provider interactions
|
22
|
+
- **rspec**: Testing framework
|
23
|
+
|
24
|
+
### Configuration System
|
25
|
+
|
26
|
+
The gem supports configuration of:
|
27
|
+
- LLM provider (default: :openai)
|
28
|
+
- Model name (default: "gpt-4o")
|
29
|
+
- Confidence threshold (default: 0.7)
|
30
|
+
- Caching enabled/disabled (default: true)
|
31
|
+
- Cache directory (default: ".botrytis_cache")
|
32
|
+
|
33
|
+
## Development Commands
|
34
|
+
|
35
|
+
### Testing
|
36
|
+
```bash
|
37
|
+
# Run all tests
|
38
|
+
bundle exec rake spec
|
39
|
+
# or
|
40
|
+
bundle exec rspec
|
41
|
+
|
42
|
+
# Run specific test files
|
43
|
+
bundle exec rspec spec/botrytis_spec.rb
|
44
|
+
|
45
|
+
# Run with specific options
|
46
|
+
bundle exec rspec --fail-fast
|
47
|
+
```
|
48
|
+
|
49
|
+
### Building and Installation
|
50
|
+
```bash
|
51
|
+
# Build the gem
|
52
|
+
bundle exec rake build
|
53
|
+
|
54
|
+
# Install locally
|
55
|
+
bundle exec rake install:local
|
56
|
+
|
57
|
+
# Clean build artifacts
|
58
|
+
bundle exec rake clean
|
59
|
+
```
|
60
|
+
|
61
|
+
### Development Setup
|
62
|
+
```bash
|
63
|
+
# Install dependencies
|
64
|
+
bundle install
|
65
|
+
|
66
|
+
# Run interactive console with gem loaded
|
67
|
+
bundle exec irb -r botrytis
|
68
|
+
```
|
69
|
+
|
70
|
+
## Semantic Matching Flow
|
71
|
+
|
72
|
+
1. Step text is compared against available step definition patterns
|
73
|
+
2. If caching is enabled, check for cached results first
|
74
|
+
3. Query LLM through Sublayer with step text and available patterns
|
75
|
+
4. LLM returns confidence score and best match pattern
|
76
|
+
5. If confidence meets threshold, return Cucumber::Glue::StepMatch
|
77
|
+
6. Cache result if caching enabled
|
78
|
+
|
79
|
+
## File Structure Notes
|
80
|
+
|
81
|
+
- Main entry point: `lib/botrytis.rb`
|
82
|
+
- Core logic in `lib/botrytis/` directory
|
83
|
+
- RSpec tests in `spec/` directory
|
84
|
+
- Gem configuration in `botrytis.gemspec`
|
85
|
+
- Rake tasks defined in `Rakefile` (default task is `:spec`)
|
data/README.md
CHANGED
@@ -1 +1,258 @@
|
|
1
|
-
# Botrytis
|
1
|
+
# Botrytis
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/botrytis)
|
4
|
+
|
5
|
+
**LLM-powered semantic matching for your Cucumber steps**
|
6
|
+
|
7
|
+
Botrytis makes your BDD tests more flexible by using Large Language Models to match semantically similar Cucumber steps, even when they don't match exactly.
|
8
|
+
|
9
|
+
## What it does
|
10
|
+
|
11
|
+
Instead of your Cucumber tests failing when step text doesn't match exactly:
|
12
|
+
|
13
|
+
```gherkin
|
14
|
+
# ❌ This fails without Botrytis
|
15
|
+
Given the user has authenticated successfully # No matching step definition
|
16
|
+
|
17
|
+
# ✅ But this step definition exists:
|
18
|
+
Given(/^the user has logged in to their account$/) do
|
19
|
+
# implementation
|
20
|
+
end
|
21
|
+
```
|
22
|
+
|
23
|
+
With Botrytis, the LLM understands that "authenticated successfully" and "logged in to their account" are semantically equivalent, so your test passes!
|
24
|
+
|
25
|
+
## Features
|
26
|
+
|
27
|
+
- 🧠 **Semantic step matching** using OpenAI, Claude, or Gemini
|
28
|
+
- 🎯 **Confidence-based matching** with configurable thresholds
|
29
|
+
- ⚡ **Intelligent caching** to avoid repeated LLM calls
|
30
|
+
- 🔄 **Parameter extraction** from semantically matched steps
|
31
|
+
- 📊 **Match reporting** shows how many fuzzy matches were found
|
32
|
+
- 🧪 **Live API testing** mode for development
|
33
|
+
|
34
|
+
## Installation
|
35
|
+
|
36
|
+
Add this line to your application's Gemfile:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
gem 'botrytis'
|
40
|
+
```
|
41
|
+
|
42
|
+
And then execute:
|
43
|
+
|
44
|
+
```bash
|
45
|
+
$ bundle install
|
46
|
+
```
|
47
|
+
|
48
|
+
Or install it yourself as:
|
49
|
+
|
50
|
+
```bash
|
51
|
+
$ gem install botrytis
|
52
|
+
```
|
53
|
+
|
54
|
+
## Quick Start
|
55
|
+
|
56
|
+
1. **Add to your Cucumber support files**:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# features/support/env.rb
|
60
|
+
require 'botrytis/cucumber'
|
61
|
+
```
|
62
|
+
|
63
|
+
2. **Configure your LLM provider** (create `features/support/botrytis.rb`):
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
require 'botrytis'
|
67
|
+
|
68
|
+
Botrytis.configure do |config|
|
69
|
+
config.llm_provider = :openai # or :claude, :gemini
|
70
|
+
config.model_name = "gpt-4o"
|
71
|
+
config.confidence_threshold = 0.7
|
72
|
+
config.cache_enabled = true
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
3. **Set your API key** (e.g., in `.env` or environment):
|
77
|
+
|
78
|
+
```bash
|
79
|
+
export OPENAI_API_KEY=your_api_key_here
|
80
|
+
```
|
81
|
+
|
82
|
+
4. **Run your tests** and see semantic matching in action!
|
83
|
+
|
84
|
+
```bash
|
85
|
+
$ bundle exec cucumber
|
86
|
+
|
87
|
+
# Output shows:
|
88
|
+
# 6 scenarios (6 passed)
|
89
|
+
# 24 steps (24 passed)
|
90
|
+
# 🎯 Botrytis Semantic Matching Summary: 10 fuzzy matches found
|
91
|
+
```
|
92
|
+
|
93
|
+
## Examples
|
94
|
+
|
95
|
+
### Authentication Variations
|
96
|
+
|
97
|
+
```gherkin
|
98
|
+
# All of these match the same step definition:
|
99
|
+
Given the user has logged in to their account # Exact match
|
100
|
+
Given the user has authenticated successfully # Semantic match ✨
|
101
|
+
Given the user has signed in to their account # Semantic match ✨
|
102
|
+
```
|
103
|
+
|
104
|
+
### Action Variations
|
105
|
+
|
106
|
+
```gherkin
|
107
|
+
# Step definition:
|
108
|
+
When(/^they click the "([^"]*)" button$/) do |button_name|
|
109
|
+
# implementation
|
110
|
+
end
|
111
|
+
|
112
|
+
# These all work:
|
113
|
+
When they click the "Buy Now" button # Exact match
|
114
|
+
When they press the "Buy Now" button # Semantic match ✨
|
115
|
+
When they tap the "Buy Now" button # Semantic match ✨
|
116
|
+
When they hit the purchase button # Semantic match ✨
|
117
|
+
When they mash the buy button # Semantic match ✨
|
118
|
+
When they gently caresses the "Buy Now" button # Semantic match ✨
|
119
|
+
```
|
120
|
+
|
121
|
+
### Assertion Variations
|
122
|
+
|
123
|
+
```gherkin
|
124
|
+
# Step definition:
|
125
|
+
Then(/^they should see a confirmation message$/) do
|
126
|
+
# implementation
|
127
|
+
end
|
128
|
+
|
129
|
+
# These all work:
|
130
|
+
Then they should see a confirmation message # Exact match
|
131
|
+
Then they should view a confirmation message # Semantic match ✨
|
132
|
+
Then they receive a success notification # Semantic match ✨
|
133
|
+
Then they get a notification # Semantic match ✨
|
134
|
+
```
|
135
|
+
|
136
|
+
## Configuration
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
Botrytis.configure do |config|
|
140
|
+
# LLM Provider (required)
|
141
|
+
config.llm_provider = :openai # :openai, :claude, or :gemini
|
142
|
+
|
143
|
+
# Model name (required)
|
144
|
+
config.model_name = "gpt-4o" # or "claude-3-sonnet", "gemini-pro", etc.
|
145
|
+
|
146
|
+
# Confidence threshold (0.0 - 1.0)
|
147
|
+
config.confidence_threshold = 0.7 # Only matches above this confidence
|
148
|
+
|
149
|
+
# Caching
|
150
|
+
config.cache_enabled = true # Cache LLM responses
|
151
|
+
config.cache_directory = ".botrytis_cache" # Cache location
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
### LLM Provider Setup
|
156
|
+
|
157
|
+
**OpenAI**:
|
158
|
+
```ruby
|
159
|
+
config.llm_provider = :openai
|
160
|
+
config.model_name = "gpt-4o" # or "gpt-4", "gpt-3.5-turbo"
|
161
|
+
# Set OPENAI_API_KEY environment variable
|
162
|
+
```
|
163
|
+
|
164
|
+
**Claude**:
|
165
|
+
```ruby
|
166
|
+
config.llm_provider = :claude
|
167
|
+
config.model_name = "claude-3-sonnet-20240229"
|
168
|
+
# Set ANTHROPIC_API_KEY environment variable
|
169
|
+
```
|
170
|
+
|
171
|
+
**Gemini**:
|
172
|
+
```ruby
|
173
|
+
config.llm_provider = :gemini
|
174
|
+
config.model_name = "gemini-pro"
|
175
|
+
# Set GOOGLE_API_KEY environment variable
|
176
|
+
```
|
177
|
+
|
178
|
+
## Development & Testing
|
179
|
+
|
180
|
+
### Running Tests
|
181
|
+
|
182
|
+
```bash
|
183
|
+
# Run all tests with mocked responses (fast)
|
184
|
+
bundle exec cucumber
|
185
|
+
|
186
|
+
# Run with live API calls (requires API key)
|
187
|
+
BOTRYTIS_LIVE_API=true bundle exec cucumber
|
188
|
+
|
189
|
+
# Run RSpec unit tests
|
190
|
+
bundle exec rspec
|
191
|
+
```
|
192
|
+
|
193
|
+
### Understanding the Output
|
194
|
+
|
195
|
+
When semantic matching occurs, you'll see a summary at the end:
|
196
|
+
|
197
|
+
```bash
|
198
|
+
🎯 Botrytis Semantic Matching Summary: 10 fuzzy matches found
|
199
|
+
```
|
200
|
+
|
201
|
+
This tells you how many steps were matched semantically vs. exactly.
|
202
|
+
|
203
|
+
### Cache Management
|
204
|
+
|
205
|
+
Botrytis caches LLM responses to improve performance:
|
206
|
+
|
207
|
+
```bash
|
208
|
+
# Clear cache
|
209
|
+
rm -rf .botrytis_cache
|
210
|
+
|
211
|
+
# Disable caching for development
|
212
|
+
Botrytis.configure do |config|
|
213
|
+
config.cache_enabled = false
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
## How It Works
|
218
|
+
|
219
|
+
1. **Step Execution**: When Cucumber can't find an exact step match, Botrytis intervenes
|
220
|
+
2. **LLM Query**: The step text and available step patterns are sent to your configured LLM
|
221
|
+
3. **Semantic Analysis**: The LLM analyzes semantic similarity and extracts parameters
|
222
|
+
4. **Confidence Check**: Only matches above the confidence threshold are used
|
223
|
+
5. **Execution**: The matched step definition runs with extracted parameters
|
224
|
+
6. **Caching**: Results are cached to avoid repeated API calls
|
225
|
+
|
226
|
+
## Requirements
|
227
|
+
|
228
|
+
- Ruby 3.1.0 or higher
|
229
|
+
- Cucumber 9.0 or higher
|
230
|
+
- Sublayer 0.2.8 or higher
|
231
|
+
- API key for your chosen LLM provider
|
232
|
+
|
233
|
+
## Contributing
|
234
|
+
|
235
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sublayerapp/botrytis.
|
236
|
+
|
237
|
+
### Development Setup
|
238
|
+
|
239
|
+
```bash
|
240
|
+
git clone https://github.com/sublayerapp/botrytis.git
|
241
|
+
cd botrytis
|
242
|
+
bundle install
|
243
|
+
|
244
|
+
# Run tests
|
245
|
+
bundle exec rake spec
|
246
|
+
bundle exec cucumber
|
247
|
+
|
248
|
+
# Build gem
|
249
|
+
bundle exec rake build
|
250
|
+
```
|
251
|
+
|
252
|
+
## License
|
253
|
+
|
254
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
255
|
+
|
256
|
+
## Why "Botrytis"?
|
257
|
+
|
258
|
+
Botrytis is a genus of fungi known for being both beneficial and parasitic - much like how this gem helps your tests pass by being a little "fuzzy" with step matching! 🍄
|
data/debug_step_args.rb
ADDED
data/future_tests.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# Future Test Ideas for Botrytis
|
2
|
+
|
3
|
+
## Parameter Translation & Extraction Testing
|
4
|
+
|
5
|
+
These are advanced test scenarios for future development that go beyond the basic semantic matching demonstrated in the blog post.
|
6
|
+
|
7
|
+
### Complex Parameter Extraction
|
8
|
+
Test the ability to extract parameters from semantically similar but structurally different steps.
|
9
|
+
|
10
|
+
**Example:**
|
11
|
+
- **Defined step**: `When I (buy|sell) (\d+) (.*) for \$(\d+\.\d+)`
|
12
|
+
- **Test cases**:
|
13
|
+
- "When I purchase 3 bananas for $2.50" → should match with params `["purchase", "3", "bananas", "2.50"]`
|
14
|
+
- "When I acquire 5 oranges for $10.00" → should match with params `["acquire", "5", "oranges", "10.00"]`
|
15
|
+
- "When I sell 2 cars for $15000.00" → should match with params `["sell", "2", "cars", "15000.00"]`
|
16
|
+
|
17
|
+
### Text-to-Number Parameter Conversion
|
18
|
+
Test semantic understanding of different number representations.
|
19
|
+
|
20
|
+
**Example:**
|
21
|
+
- **Defined step**: `Given I have (\d+) apples`
|
22
|
+
- **Test cases**:
|
23
|
+
- "Given I have five apples" → should extract `"5"`
|
24
|
+
- "Given I possess a dozen apples" → should extract `"12"`
|
25
|
+
- "Given I own several apples" → should handle ambiguous quantities
|
26
|
+
|
27
|
+
### Date/Time Parameter Semantic Matching
|
28
|
+
Test interpretation of different time expressions.
|
29
|
+
|
30
|
+
**Example:**
|
31
|
+
- **Defined step**: `When I schedule a meeting for (\d{4}-\d{2}-\d{2})`
|
32
|
+
- **Test cases**:
|
33
|
+
- "When I schedule a meeting for tomorrow" → should convert to actual date
|
34
|
+
- "When I schedule a meeting for next Friday" → should convert to appropriate date
|
35
|
+
- "When I schedule a meeting for Christmas" → should handle holiday conversion
|
36
|
+
|
37
|
+
### Multiple Parameter Reordering
|
38
|
+
Test ability to match steps where parameters appear in different orders.
|
39
|
+
|
40
|
+
**Example:**
|
41
|
+
- **Defined step**: `Given (\w+) has (\d+) (.*) in their (\w+)`
|
42
|
+
- **Test cases**:
|
43
|
+
- "Given Alice has 5 books in their backpack" → `["Alice", "5", "books", "backpack"]`
|
44
|
+
- "Given there are 3 pencils in Bob's drawer" → should reorder to `["Bob", "3", "pencils", "drawer"]`
|
45
|
+
|
46
|
+
## Advanced Confidence Testing
|
47
|
+
|
48
|
+
### Confidence Threshold Edge Cases
|
49
|
+
Test behavior at various confidence levels to validate threshold settings.
|
50
|
+
|
51
|
+
- Steps that should match at 0.9+ confidence
|
52
|
+
- Steps that should match at 0.7-0.8 confidence
|
53
|
+
- Steps that should be rejected below 0.7 confidence
|
54
|
+
- Borderline cases that test threshold boundaries
|
55
|
+
|
56
|
+
### Ambiguous Step Resolution
|
57
|
+
Test handling when multiple step definitions could match with similar confidence.
|
58
|
+
|
59
|
+
**Example:**
|
60
|
+
- **Defined steps**:
|
61
|
+
- `When I click the save button`
|
62
|
+
- `When I click the submit button`
|
63
|
+
- **Ambiguous input**: "When I click the confirm button"
|
64
|
+
- **Expected behavior**: Either pick highest confidence match or request clarification
|
65
|
+
|
66
|
+
## Performance & Scale Testing
|
67
|
+
|
68
|
+
### Large Step Definition Sets
|
69
|
+
Test performance with hundreds of step definitions to ensure semantic matching scales.
|
70
|
+
|
71
|
+
### Caching Effectiveness
|
72
|
+
Validate that caching improves performance for repeated semantic matches.
|
73
|
+
|
74
|
+
### LLM Provider Comparison
|
75
|
+
Test semantic matching quality across different LLM providers (OpenAI, Anthropic, local models).
|
76
|
+
|
77
|
+
## Error Handling & Resilience
|
78
|
+
|
79
|
+
### LLM Service Failures
|
80
|
+
Test graceful degradation when LLM service is unavailable:
|
81
|
+
- Should fall back to exact matching
|
82
|
+
- Should provide helpful error messages
|
83
|
+
- Should not crash the test suite
|
84
|
+
|
85
|
+
### Malformed LLM Responses
|
86
|
+
Test handling of unexpected LLM response formats:
|
87
|
+
- Invalid JSON responses
|
88
|
+
- Missing required fields
|
89
|
+
- Confidence scores outside 0.0-1.0 range
|
90
|
+
|
91
|
+
### Network Timeout Scenarios
|
92
|
+
Test behavior under poor network conditions:
|
93
|
+
- Slow LLM responses
|
94
|
+
- Connection timeouts
|
95
|
+
- Retry logic validation
|
96
|
+
|
97
|
+
## Integration with BDD Tools
|
98
|
+
|
99
|
+
### Multiple Cucumber Versions
|
100
|
+
Test compatibility across different versions of Cucumber gem.
|
101
|
+
|
102
|
+
### Other BDD Frameworks
|
103
|
+
Explore integration with:
|
104
|
+
- RSpec feature specs
|
105
|
+
- Turnip
|
106
|
+
- Spinach
|
107
|
+
|
108
|
+
### IDE Integration
|
109
|
+
Test semantic matching in development environments:
|
110
|
+
- Step definition discovery in IDEs
|
111
|
+
- Autocomplete with semantic suggestions
|
112
|
+
- Real-time matching feedback
|
113
|
+
|
114
|
+
## Real-World Scenario Testing
|
115
|
+
|
116
|
+
### Business Domain Vocabularies
|
117
|
+
Test semantic matching within specific business contexts:
|
118
|
+
- E-commerce scenarios (buy/purchase/order)
|
119
|
+
- Financial scenarios (pay/transfer/deposit)
|
120
|
+
- Healthcare scenarios (diagnose/treat/prescribe)
|
121
|
+
|
122
|
+
### Multi-language Step Definitions
|
123
|
+
Test semantic matching across different natural languages:
|
124
|
+
- English variations
|
125
|
+
- Formal vs informal language
|
126
|
+
- Technical vs business terminology
|
127
|
+
|
128
|
+
## Security & Privacy Considerations
|
129
|
+
|
130
|
+
### Sensitive Data in Steps
|
131
|
+
Ensure no sensitive information is sent to LLM providers:
|
132
|
+
- Test with steps containing mock credentials
|
133
|
+
- Validate data sanitization
|
134
|
+
- Test privacy-preserving modes
|
135
|
+
|
136
|
+
### LLM Provider Data Retention
|
137
|
+
Understand and test implications of different LLM providers' data policies.
|
138
|
+
|
139
|
+
## Conclusion
|
140
|
+
|
141
|
+
These advanced test scenarios will help ensure Botrytis becomes a robust, production-ready tool for semantic step matching. They build upon the basic functionality demonstrated in the blog post examples.
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'cucumber'
|
2
|
+
require 'botrytis'
|
3
|
+
|
4
|
+
module Botrytis
|
5
|
+
module CucumberIntegration
|
6
|
+
@@step_definitions = []
|
7
|
+
@@semantic_matcher = nil
|
8
|
+
@@semantic_matches_count = 0
|
9
|
+
@@total_step_attempts = 0
|
10
|
+
|
11
|
+
def self.install!
|
12
|
+
# Hook into step matching process instead of undefined creation
|
13
|
+
Cucumber::Glue::RegistryAndMore.prepend(SemanticStepMatcher)
|
14
|
+
|
15
|
+
# Hook into step definition registration to collect them
|
16
|
+
Cucumber::Glue::StepDefinition.prepend(StepDefinitionCollector)
|
17
|
+
|
18
|
+
# Initialize semantic matcher
|
19
|
+
@@semantic_matcher = Botrytis::SemanticMatcher.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.step_definitions
|
23
|
+
@@step_definitions
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.semantic_matcher
|
27
|
+
@@semantic_matcher
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.record_semantic_match!
|
31
|
+
@@semantic_matches_count += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.record_step_attempt!
|
35
|
+
@@total_step_attempts += 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.semantic_matches_count
|
39
|
+
@@semantic_matches_count
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.total_step_attempts
|
43
|
+
@@total_step_attempts
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.print_summary
|
47
|
+
if @@semantic_matches_count > 0
|
48
|
+
puts "\n🎯 Botrytis Semantic Matching Summary: #{@@semantic_matches_count} fuzzy matches found"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module StepDefinitionCollector
|
53
|
+
def initialize(*args)
|
54
|
+
super
|
55
|
+
# Add this step definition to our collection
|
56
|
+
Botrytis::CucumberIntegration.add_step_definition(self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.add_step_definition(step_def)
|
61
|
+
# Create an adapter to make the step definition compatible with semantic matcher
|
62
|
+
adapter = StepDefinitionAdapter.new(step_def)
|
63
|
+
@@step_definitions << adapter
|
64
|
+
end
|
65
|
+
|
66
|
+
# Adapter to make modern Cucumber StepDefinition compatible with semantic matcher
|
67
|
+
class StepDefinitionAdapter
|
68
|
+
def initialize(step_def)
|
69
|
+
@step_def = step_def
|
70
|
+
end
|
71
|
+
|
72
|
+
def regexp_source
|
73
|
+
@step_def.expression.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def proc
|
77
|
+
# Create a proc that delegates to the step definition
|
78
|
+
lambda { |*args| @step_def.invoke(nil, *args) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def method_missing(method, *args, &block)
|
82
|
+
@step_def.send(method, *args, &block)
|
83
|
+
end
|
84
|
+
|
85
|
+
def respond_to_missing?(method, include_private = false)
|
86
|
+
@step_def.respond_to?(method, include_private)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
module SemanticStepMatcher
|
91
|
+
def step_matches(name_to_match)
|
92
|
+
# First try the normal step matching
|
93
|
+
matches = super(name_to_match)
|
94
|
+
|
95
|
+
if matches.any?
|
96
|
+
return matches
|
97
|
+
end
|
98
|
+
|
99
|
+
# If no exact matches, try semantic matching
|
100
|
+
# Track semantic match attempts
|
101
|
+
Botrytis::CucumberIntegration.record_step_attempt!
|
102
|
+
semantic_match = attempt_semantic_match(name_to_match)
|
103
|
+
|
104
|
+
if semantic_match
|
105
|
+
# Semantic match found
|
106
|
+
Botrytis::CucumberIntegration.record_semantic_match!
|
107
|
+
return [semantic_match]
|
108
|
+
else
|
109
|
+
# No semantic match found
|
110
|
+
return matches # Return empty array, which will result in undefined step
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def attempt_semantic_match(step_name)
|
117
|
+
step_definitions = Botrytis::CucumberIntegration.step_definitions
|
118
|
+
semantic_matcher = Botrytis::CucumberIntegration.semantic_matcher
|
119
|
+
|
120
|
+
return nil if step_definitions.empty? || semantic_matcher.nil?
|
121
|
+
|
122
|
+
# Use the semantic matcher to find a match
|
123
|
+
match = semantic_matcher.find_match(step_name, step_definitions)
|
124
|
+
|
125
|
+
return match if match
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Install the semantic matching when this file is loaded
|
133
|
+
Botrytis::CucumberIntegration.install!
|
134
|
+
|
135
|
+
# Custom StepMatch class for semantic matches that avoids display corruption
|
136
|
+
# The core issue is that Cucumber tries to highlight parameters in step text
|
137
|
+
# based on parameter positions from the constructed text, but the positions
|
138
|
+
# don't align with the original step text, causing garbled display.
|
139
|
+
class SemanticStepMatch < Cucumber::StepMatch
|
140
|
+
def replace_arguments(step_name, format, colour)
|
141
|
+
# For semantic matches, don't try to replace/highlight arguments
|
142
|
+
# Just return the original step name to avoid garbled text from
|
143
|
+
# parameter position mismatches
|
144
|
+
step_name
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Print summary at exit
|
149
|
+
at_exit do
|
150
|
+
Botrytis::CucumberIntegration.print_summary
|
151
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'cucumber/formatter/console'
|
2
|
+
require_relative '../botrytis'
|
3
|
+
|
4
|
+
module Botrytis
|
5
|
+
class Formatter
|
6
|
+
include Cucumber::Formatter::Console
|
7
|
+
|
8
|
+
def initialize(config)
|
9
|
+
@config = config
|
10
|
+
|
11
|
+
# Initialize Botrytis configuration if not already done
|
12
|
+
unless defined?(Botrytis.configuration)
|
13
|
+
Botrytis.configure do |botrytis_config|
|
14
|
+
botrytis_config.confidence_threshold = 0.7
|
15
|
+
botrytis_config.cache_enabled = false
|
16
|
+
botrytis_config.llm_provider = :openai
|
17
|
+
botrytis_config.model_name = "gpt-4o"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
@semantic_matcher = SemanticMatcher.new
|
22
|
+
@step_definitions = []
|
23
|
+
|
24
|
+
# Use the modern event system to collect step definitions
|
25
|
+
config.on_event(:step_definition_registered) do |event|
|
26
|
+
@step_definitions << event.step_definition
|
27
|
+
end
|
28
|
+
|
29
|
+
# Register semantic matcher once all setup is done
|
30
|
+
config.on_event(:test_run_started) do |event|
|
31
|
+
register_semantic_matcher
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def register_semantic_matcher
|
36
|
+
# Find the correct StepDefinition class to monkey patch
|
37
|
+
step_def_class = if defined?(Cucumber::Glue::StepDefinition)
|
38
|
+
Cucumber::Glue::StepDefinition
|
39
|
+
elsif defined?(Cucumber::StepDefinition)
|
40
|
+
Cucumber::StepDefinition
|
41
|
+
else
|
42
|
+
# Try to find any step definition class
|
43
|
+
Cucumber.constants.select do |c|
|
44
|
+
Cucumber.const_get(c).is_a?(Class) && c.to_s.include?('Step')
|
45
|
+
end.first&.then { |c| Cucumber.const_get(c) }
|
46
|
+
end
|
47
|
+
|
48
|
+
return unless step_def_class
|
49
|
+
|
50
|
+
@original_match_method = step_def_class.instance_method(:match)
|
51
|
+
|
52
|
+
semantic_matcher = @semantic_matcher
|
53
|
+
step_definitions = @step_definitions
|
54
|
+
|
55
|
+
step_def_class.define_method(:match) do |step_name|
|
56
|
+
result = @original_match_method.bind(self).call(step_name)
|
57
|
+
|
58
|
+
if result.nil?
|
59
|
+
puts "\n🥒 Botrytis is looking for a fuzzy match for: \"#{step_name}\""
|
60
|
+
|
61
|
+
match = semantic_matcher.find_match(step_name, step_definitions)
|
62
|
+
|
63
|
+
if match
|
64
|
+
puts "✅ Found a semantic match!"
|
65
|
+
return match
|
66
|
+
else
|
67
|
+
puts "❌ No semantic match found"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -6,7 +6,45 @@ module Botrytis
|
|
6
6
|
name: "step_match_result",
|
7
7
|
description: "Results of semantic matching for a cucumber step",
|
8
8
|
attributes: [
|
9
|
-
{ name: "
|
9
|
+
{ name: "step_text_analysis", description: "Analysis of the step text, including semantic meaning and intent" },
|
10
|
+
{ name: "match_found", description: "Indicates if a match was found, either the string yes or no" },
|
11
|
+
{ name: "best_match_pattern", description: "The pattern that best matches semantically" },
|
12
|
+
{ name: "confidence", description: "Confidence score of the match (0.0 - 1.0)" },
|
13
|
+
{ name: "parameter_values", description: "A comma separated list of parameter values extracted from the match" }
|
10
14
|
]
|
15
|
+
|
16
|
+
def initialize(step_text:, available_patterns:)
|
17
|
+
@step_text = step_text
|
18
|
+
@available_patterns = available_patterns
|
19
|
+
end
|
20
|
+
|
21
|
+
def generate
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
25
|
+
def prompt
|
26
|
+
<<-PROMPT
|
27
|
+
You are a semantic matcher for Cucumber step definitions. Your task is to determine if a step text semantically matches one of the available regex patterns, even if it doesn't match exactly.
|
28
|
+
|
29
|
+
Step Text: "#{@step_text}"
|
30
|
+
|
31
|
+
Available Step Definition Patterns:
|
32
|
+
#{@available_patterns.join("\n")}
|
33
|
+
|
34
|
+
For each pattern, consider:
|
35
|
+
1. The semantic meaning/intent of the step
|
36
|
+
2. The structure of the pattern
|
37
|
+
3. Any parameters that would need to be extracted
|
38
|
+
|
39
|
+
Choose the pattern that best matches the step text semantically.
|
40
|
+
If no pattern is a good semantic match, indicate that no match was found.
|
41
|
+
|
42
|
+
If you find a match, extract any parameters that would be captured by the pattern. And return them as a comma separated list.
|
43
|
+
For example, if the pattern is "I have (\\d+) cucumbers" and the step is "I have 5 cucumbers",
|
44
|
+
the parameter value would be "5".
|
45
|
+
|
46
|
+
Provide your confidence in the match as a value between 0.0 (no confidence) and 1.0 (absolute certainty).
|
47
|
+
PROMPT
|
48
|
+
end
|
11
49
|
end
|
12
50
|
end
|
@@ -0,0 +1,261 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'json'
|
4
|
+
require 'botrytis/semantic_match_generator'
|
5
|
+
|
6
|
+
module Botrytis
|
7
|
+
class SemanticMatcher
|
8
|
+
def initialize
|
9
|
+
ensure_cache_directory if Botrytis.configuration.cache_enabled
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_match(step_text, available_step_definitions)
|
13
|
+
patterns = available_step_definitions.map do |step_def|
|
14
|
+
{
|
15
|
+
pattern: step_def.regexp_source,
|
16
|
+
proc: step_def.proc,
|
17
|
+
step_def: step_def
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Filter out test verification steps for semantic matching
|
22
|
+
# These are steps that end with "should have executed" or similar test patterns
|
23
|
+
business_patterns = patterns.reject do |p|
|
24
|
+
pattern_text = p[:pattern].to_s
|
25
|
+
pattern_text.include?("should have executed") ||
|
26
|
+
pattern_text.include?("configured for testing") ||
|
27
|
+
pattern_text.include?("test") ||
|
28
|
+
pattern_text.include?("verification")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Use business patterns for LLM matching, but keep all patterns for final matching
|
32
|
+
query_patterns = business_patterns.empty? ? patterns : business_patterns
|
33
|
+
|
34
|
+
if Botrytis.configuration.cache_enabled
|
35
|
+
cache_result = check_cache(step_text, patterns.map { |p| p[:pattern] })
|
36
|
+
return cache_result if cache_result
|
37
|
+
end
|
38
|
+
|
39
|
+
match_result = query_llm(step_text, query_patterns.map { |p| p[:pattern] })
|
40
|
+
|
41
|
+
save_to_cache(step_text, patterns.map { |p| p[:pattern] }, match_result) if Botrytis.configuration.cache_enabled
|
42
|
+
|
43
|
+
if match_result.match_found == "yes" && match_result.confidence.to_f >= Botrytis.configuration.confidence_threshold
|
44
|
+
|
45
|
+
# Handle different pattern formats from LLM response
|
46
|
+
# The LLM might return escaped quotes or slightly different formats
|
47
|
+
matching_pattern = patterns.find do |p|
|
48
|
+
original_pattern = p[:pattern]
|
49
|
+
llm_pattern = match_result.best_match_pattern
|
50
|
+
|
51
|
+
# Try exact match first
|
52
|
+
original_pattern == llm_pattern ||
|
53
|
+
# Try with/without surrounding slashes
|
54
|
+
original_pattern == "/#{llm_pattern}/" ||
|
55
|
+
original_pattern.gsub(/^\/|\/$/,'') == llm_pattern.gsub(/^\/|\/$/,'') ||
|
56
|
+
# Try with unescaped quotes (LLM might escape them)
|
57
|
+
original_pattern == llm_pattern.gsub(/\\\"/, '"') ||
|
58
|
+
original_pattern.gsub(/^\/|\/$/,'') == llm_pattern.gsub(/^\/|\/$/,'').gsub(/\\\"/, '"')
|
59
|
+
end
|
60
|
+
|
61
|
+
if matching_pattern
|
62
|
+
# Convert comma-separated parameter_values string to array
|
63
|
+
parameter_values = if match_result.parameter_values.nil? || match_result.parameter_values.empty?
|
64
|
+
[]
|
65
|
+
else
|
66
|
+
match_result.parameter_values.split(',').map(&:strip)
|
67
|
+
end
|
68
|
+
return create_match_result(matching_pattern[:step_def], step_text, parameter_values)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def query_llm(step_text, patterns)
|
78
|
+
generator = SemanticMatchGenerator.new(
|
79
|
+
step_text: step_text,
|
80
|
+
available_patterns: patterns
|
81
|
+
)
|
82
|
+
|
83
|
+
case Botrytis.configuration.llm_provider
|
84
|
+
when :openai
|
85
|
+
Sublayer.configuration.ai_provider = Sublayer::Providers::OpenAI
|
86
|
+
when :claude
|
87
|
+
Sublayer.configuration.ai_provider = Sublayer::Providers::Claude
|
88
|
+
when :gemini
|
89
|
+
Sublayer.configuration.ai_provider = Sublayer::Providers::Gemini
|
90
|
+
end
|
91
|
+
|
92
|
+
Sublayer.configuration.ai_model = Botrytis.configuration.model_name
|
93
|
+
|
94
|
+
begin
|
95
|
+
result = generator.generate
|
96
|
+
result
|
97
|
+
rescue => e
|
98
|
+
# LLM API Error occurred, falling back to no match
|
99
|
+
# Return a "no match" response if API fails
|
100
|
+
OpenStruct.new(
|
101
|
+
match_found: "no",
|
102
|
+
best_match_pattern: "",
|
103
|
+
confidence: "0.0",
|
104
|
+
parameter_values: ""
|
105
|
+
)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def ensure_cache_directory
|
110
|
+
FileUtils.mkdir_p(Botrytis.configuration.cache_directory) unless Dir.exist?(Botrytis.configuration.cache_directory)
|
111
|
+
end
|
112
|
+
|
113
|
+
def cache_key(step_text, patterns)
|
114
|
+
Digest::MD5.hexdigest("#{step_text}-#{patterns.sort.join('-')}")
|
115
|
+
end
|
116
|
+
|
117
|
+
def check_cache(step_text, patterns)
|
118
|
+
key = cache_key(step_text, patterns)
|
119
|
+
cache_file = File.join(Botrytis.configuration.cache_directory, "#{key}.json")
|
120
|
+
|
121
|
+
if File.exist?(cache_file)
|
122
|
+
data = JSON.parse(File.read(cache_file))
|
123
|
+
end
|
124
|
+
|
125
|
+
nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def create_match_result(step_definition, step_text, parameter_values)
|
129
|
+
# Instead of trying to manually create step arguments, let's use Cucumber's
|
130
|
+
# normal matching mechanism by having the step definition actually match
|
131
|
+
# a constructed step text that would produce the right parameters
|
132
|
+
|
133
|
+
begin
|
134
|
+
# Try to construct a step text that the step definition would actually match
|
135
|
+
constructed_step_text = construct_matching_step_text_for_step_def(step_definition, parameter_values)
|
136
|
+
|
137
|
+
# Use the step definition's normal matching mechanism
|
138
|
+
if step_definition.respond_to?(:arguments_from)
|
139
|
+
# This is the normal way Cucumber creates step matches
|
140
|
+
step_arguments = step_definition.arguments_from(constructed_step_text)
|
141
|
+
return SemanticStepMatch.new(step_definition, step_text, step_arguments)
|
142
|
+
else
|
143
|
+
# Fallback to manual creation
|
144
|
+
step_arguments = create_original_step_arguments(step_definition, parameter_values)
|
145
|
+
return SemanticStepMatch.new(step_definition, step_text, step_arguments)
|
146
|
+
end
|
147
|
+
rescue => e
|
148
|
+
# Error in create_match_result, falling back
|
149
|
+
# Fallback to simple creation with empty arguments
|
150
|
+
return SemanticStepMatch.new(step_definition, step_text, [])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def create_proper_step_arguments(step_definition, step_text, parameter_values)
|
155
|
+
# Create step arguments that don't interfere with display formatting
|
156
|
+
# For semantic matching, we just need the parameter values to be passed to the step
|
157
|
+
# We don't need complex MatchData objects since Cucumber will handle display
|
158
|
+
return parameter_values || []
|
159
|
+
end
|
160
|
+
|
161
|
+
def construct_matching_step_text_for_step_def(step_definition, parameter_values)
|
162
|
+
# This creates a step text that would actually match the step definition's regex
|
163
|
+
# and produce the desired parameter values
|
164
|
+
|
165
|
+
if parameter_values.nil? || parameter_values.empty?
|
166
|
+
# For steps without parameters, just use the regexp source without anchors
|
167
|
+
if step_definition.respond_to?(:regexp_source)
|
168
|
+
source = step_definition.regexp_source.to_s
|
169
|
+
return source.gsub(/^\/\^/, '').gsub(/\$\/$/, '').gsub(/[\^$\/]/, '')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# For the button example: /^they click the "([^"]*)" button$/
|
174
|
+
# We want to produce: they click the "Buy Now" button
|
175
|
+
# So that when matched, it captures "Buy Now"
|
176
|
+
|
177
|
+
if step_definition.respond_to?(:expression) && step_definition.expression.is_a?(Regexp)
|
178
|
+
regex = step_definition.expression
|
179
|
+
elsif step_definition.respond_to?(:regexp_source)
|
180
|
+
source = step_definition.regexp_source.to_s.gsub(/^\/\^/, '').gsub(/\$\/$/, '')
|
181
|
+
regex = Regexp.new("^#{source}$")
|
182
|
+
else
|
183
|
+
# Fallback - return something simple
|
184
|
+
return parameter_values.join(' ')
|
185
|
+
end
|
186
|
+
|
187
|
+
# Better approach: build step text by understanding the regex structure
|
188
|
+
pattern = regex.source
|
189
|
+
|
190
|
+
# For simple cases like they click the "([^"]*)" button
|
191
|
+
# We want to replace ([^"]*) with the actual parameter value
|
192
|
+
if pattern.include?('"([^"]*)"') && parameter_values.length == 1
|
193
|
+
# Handle quoted parameter patterns specifically
|
194
|
+
result = pattern.gsub(/\([^)]+\)/, parameter_values[0])
|
195
|
+
else
|
196
|
+
# Fallback to general replacement
|
197
|
+
parameter_values.each do |value|
|
198
|
+
pattern = pattern.sub(/\([^)]+\)/, value)
|
199
|
+
end
|
200
|
+
result = pattern
|
201
|
+
end
|
202
|
+
|
203
|
+
# Clean up anchors and regex chars
|
204
|
+
result.gsub(/[\^$]/, '').gsub(/^\//, '').gsub(/\/$/, '')
|
205
|
+
end
|
206
|
+
|
207
|
+
def create_original_step_arguments(step_definition, parameter_values)
|
208
|
+
# For semantic matching, create simple argument objects that work with Cucumber
|
209
|
+
# This avoids the complex text construction that causes display issues
|
210
|
+
return [] if parameter_values.nil? || parameter_values.empty?
|
211
|
+
|
212
|
+
# Create simple argument objects that just hold the parameter values
|
213
|
+
# without trying to map them to text positions
|
214
|
+
parameter_values.map do |value|
|
215
|
+
# Create a minimal object that responds to the methods Cucumber expects
|
216
|
+
StepArgument.new(value)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# Minimal step argument class for semantic matching
|
221
|
+
class StepArgument
|
222
|
+
def initialize(value)
|
223
|
+
@value = value
|
224
|
+
end
|
225
|
+
|
226
|
+
def group(index = 0)
|
227
|
+
index == 0 ? @value : nil
|
228
|
+
end
|
229
|
+
|
230
|
+
def to_s
|
231
|
+
@value.to_s
|
232
|
+
end
|
233
|
+
|
234
|
+
def value
|
235
|
+
@value
|
236
|
+
end
|
237
|
+
|
238
|
+
def captures
|
239
|
+
[@value]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def construct_matching_step_text(regex, parameter_values)
|
244
|
+
# This is a simple approach: take the regex pattern and substitute
|
245
|
+
# capture groups with our parameter values
|
246
|
+
pattern = regex.source
|
247
|
+
|
248
|
+
# Replace capture groups like ([^"]*) with actual values
|
249
|
+
parameter_values.each_with_index do |value, index|
|
250
|
+
# Replace the first capture group with the parameter value
|
251
|
+
pattern = pattern.sub(/\([^)]+\)/, value)
|
252
|
+
end
|
253
|
+
|
254
|
+
# Clean up the pattern to make it a valid step text
|
255
|
+
pattern = pattern.gsub(/[\^$]/, '') # Remove anchors
|
256
|
+
pattern
|
257
|
+
end
|
258
|
+
|
259
|
+
|
260
|
+
end
|
261
|
+
end
|
data/lib/botrytis/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: botrytis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Werner
|
@@ -59,13 +59,20 @@ executables: []
|
|
59
59
|
extensions: []
|
60
60
|
extra_rdoc_files: []
|
61
61
|
files:
|
62
|
+
- ".claude/settings.local.json"
|
62
63
|
- ".rspec"
|
64
|
+
- CLAUDE.md
|
63
65
|
- LICENSE
|
64
66
|
- README.md
|
65
67
|
- Rakefile
|
68
|
+
- debug_step_args.rb
|
69
|
+
- future_tests.md
|
66
70
|
- lib/botrytis.rb
|
67
71
|
- lib/botrytis/configuration.rb
|
72
|
+
- lib/botrytis/cucumber.rb
|
73
|
+
- lib/botrytis/formatter.rb
|
68
74
|
- lib/botrytis/semantic_match_generator.rb
|
75
|
+
- lib/botrytis/semantic_matcher.rb
|
69
76
|
- lib/botrytis/version.rb
|
70
77
|
- sig/botrytis.rbs
|
71
78
|
homepage: https://github.com/sublayerapp/botrytis
|