-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial commit for REDCap Cypress BDD + AI
- Loading branch information
0 parents
commit 3b63ebd
Showing
14 changed files
with
598 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
# Node.js | ||
node_modules/ | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
/package-lock.json | ||
/.pnp | ||
.pnp.js | ||
|
||
# Dependency directories | ||
bower_components/ | ||
|
||
# Cypress logs and screenshots | ||
cypress/screenshots/ | ||
cypress/videos/ | ||
results/ | ||
|
||
# dotenv environment variables file | ||
.env | ||
.env.local | ||
.env.*.local | ||
|
||
# Cypress local settings (to prevent committing Cypress-related settings that are local) | ||
cypress.json | ||
cypress.env.json | ||
|
||
# Build artifacts (general) | ||
dist/ | ||
build/ | ||
out/ | ||
coverage/ | ||
*.log | ||
|
||
# Python bytecode | ||
*.py[cod] | ||
__pycache__/ | ||
*.so | ||
*.dylib | ||
|
||
# Virtual environments | ||
.venv/ | ||
venv/ | ||
ENV/ | ||
env.bak/ | ||
env/ | ||
|
||
# Python-specific packaging | ||
*.egg | ||
*.egg-info/ | ||
dist/ | ||
build/ | ||
eggs/ | ||
lib/ | ||
lib64/ | ||
parts/ | ||
sdist/ | ||
var/ | ||
*.tgz | ||
*.bak | ||
*.manifest | ||
*.spec | ||
*.lock | ||
|
||
# PyInstaller | ||
*.manifest | ||
*.spec | ||
|
||
# Installer logs | ||
pip-log.txt | ||
pip-delete-this-directory.txt | ||
|
||
# Pyre type checker | ||
.pyre/ | ||
|
||
# macOS | ||
.DS_Store | ||
|
||
# VS Code | ||
.vscode/ | ||
|
||
# JetBrains IDEs | ||
.idea/ | ||
|
||
# Docker related files | ||
Dockerfile | ||
docker-compose.override.yml | ||
|
||
# Pytest | ||
.pytest_cache/ | ||
htmlcov/ | ||
.tox/ | ||
|
||
# Jest | ||
/coverage/ | ||
|
||
# Parcel | ||
.cache/ | ||
.parcel-cache/ | ||
|
||
# Lock files | ||
*.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
# Cypress BDD with AI | ||
|
||
This project implements BDD (Behavior-Driven Development) testing using Cypress. The setup leverages Docker to create consistent testing environments and integrates AI-driven Python scripts for utility automation, including step definition generation and DOM verification. | ||
|
||
## Project Goals | ||
|
||
- **BDD Testing**: Use Cypress for end-to-end testing with a behavior-driven approach. | ||
- **Dockerized Setup**: Ensure reproducibility and isolated environments using Docker Compose. | ||
- **AI Integration**: Automate step generation and DOM element verification with Python scripts interacting with AI: | ||
- Automatically generate step definitions from feature files. | ||
- Ensure that utility functions (selectors, DOM interactions) remain synchronized with the latest REDCap UI changes. | ||
|
||
## Services Overview | ||
|
||
The project is orchestrated with Docker Compose and has two main services: | ||
|
||
- **ai\_scripts**: Handles AI-driven file generation and DOM consistency checks, ensuring the `redcap_dom.js` utility functions remain aligned with REDCap's structure. | ||
- **cypress**: Executes headless BDD tests and is integrated into the CI/CD pipeline. | ||
|
||
Additionally, Cypress can be run locally in an interactive UI mode for test development and debugging. | ||
|
||
## Interaction with REDCap | ||
|
||
This project is designed to interact with a Dockerized REDCap instance. Ensure the REDCap instance is running and accessible at the `CYPRESS_BASE_URL` before executing Cypress tests. The URL should point to the REDCap instance's address and port. | ||
|
||
### Prerequisites | ||
|
||
Ensure the following are installed: | ||
|
||
- [Docker](https://www.docker.com/) | ||
- [Docker Compose](https://docs.docker.com/compose/) | ||
- [Node.js and npm](https://nodejs.org/) (for local Cypress UI) | ||
- [Python 3](https://www.python.org/) (for running AI scripts) | ||
|
||
### Environment Variables | ||
|
||
Create a `.env` file in the root directory of your project with the following variables: | ||
|
||
```env | ||
CYPRESS_BASE_URL=http://localhost:80 | ||
CYPRESS_USERNAME=your-username | ||
CYPRESS_PASSWORD=your-password | ||
GPT_ENDPOINT=https://your-institution-gpt-endpoint | ||
SUBSCRIPTION_KEY=your-subscription-key | ||
``` | ||
|
||
### Key Features | ||
|
||
- **Cypress UI Mode**: For local testing and debugging. | ||
- **Headless Mode**: For CI/CD pipelines, executed via Docker Compose. | ||
- **AI Scripts**: Generate Cypress step definitions and verify utility functions, keeping your test and DOM interactions up to date. | ||
|
||
|
||
## Gherkin Syntax and Usage | ||
|
||
Gherkin is a language used to write feature files for BDD. It is designed to be easy to understand by non-developers, providing a clear syntax for specifying test scenarios. | ||
|
||
### Basic Syntax | ||
|
||
Gherkin uses several keywords to define features, scenarios, and steps: | ||
|
||
- **Feature**: Describes the feature being tested. | ||
- **Scenario**: Represents a specific test case or example. | ||
- **Given**, **When**, **Then**, **And**, **But**: Define steps within a scenario. | ||
|
||
### Example | ||
|
||
Here’s an example of a Gherkin feature file to test user login functionality with parameterized username and password: | ||
|
||
**login.feature**: | ||
```gherkin | ||
Feature: User Login | ||
Scenario: Successful login with valid credentials | ||
Given the user navigates to the login page | ||
When the user enters username "user1" and password "password123" | ||
And the user clicks the login button | ||
Then the user should be redirected to the dashboard | ||
And the user should see the welcome message "Welcome, user1!" | ||
``` | ||
|
||
### Storing Gherkin Files | ||
|
||
In this system, Gherkin feature files are stored in the \`e2e/\` directory. You can create multiple \`.feature\` files for different scenarios and features. | ||
|
||
### Transforming Gherkin to Steps | ||
|
||
AI scripts are used to transform Gherkin feature files into step definitions needed for Cypress to execute the tests. The \`ai_worker.py\` script is responsible for this transformation. | ||
|
||
Here’s an example of what the above Gherkin scenario might transform into: | ||
|
||
**login_steps.js**: | ||
```js | ||
const { Given, When, Then } = require('cypress-cucumber-preprocessor/steps'); | ||
|
||
Given('the user navigates to the login page', () => { | ||
cy.visit('/login'); | ||
}); | ||
|
||
When('the user enters username {string} and password {string}', (username, password) => { | ||
cy.get('#username').type(username); | ||
cy.get('#password').type(password); | ||
}); | ||
|
||
When('the user clicks the login button', () => { | ||
cy.get('button[type=submit]').click(); | ||
}); | ||
|
||
Then('the user should be redirected to the dashboard', () => { | ||
cy.url().should('include', '/dashboard'); | ||
}); | ||
|
||
Then('the user should see the welcome message {string}', (welcomeMessage) => { | ||
cy.contains(welcomeMessage).should('be.visible'); | ||
}); | ||
``` | ||
|
||
## Docker Setup | ||
|
||
Build the Docker Containers: | ||
|
||
``` | ||
docker compose build | ||
``` | ||
|
||
|
||
## Usage | ||
|
||
### Running Tests | ||
|
||
You have two options for running Cypress tests. | ||
|
||
#### 1. Running Tests in Headless Mode (One-Time Execution) | ||
|
||
This option runs the tests once in headless mode and automatically shuts down the container when the tests finish. You don't need to start the services separately for this. | ||
|
||
To run the tests in headless mode: | ||
``` | ||
docker compose up cypress | ||
``` | ||
|
||
|
||
#### 2. Running Tests with Cypress UI (Interactive Mode) | ||
|
||
This option opens the Cypress UI, where you can interactively view and debug tests. For this, you need to run the tests locally (outside of Docker) using `npx` after installing the necessary dependencies: | ||
|
||
First, ensure you have installed Cypress locally: | ||
``` | ||
cd /cypress/ | ||
npm install | ||
``` | ||
Then, run Cypress in interactive mode: | ||
``` | ||
npx cypress open | ||
``` | ||
|
||
|
||
|
||
### AI Scripts | ||
|
||
The 'ai' service is designed for one-time executions to assist in generating the necessary files locally. | ||
The generated files should then be checked into version control for use in testing. | ||
|
||
**update_utilities.sh**: Updates utility functions based on the latest page structure. | ||
``` | ||
docker compose run --rm ai sh /app/ai_scripts/update_utilities.sh | ||
``` | ||
|
||
**regenerate_steps.sh**: Regenerates step definitions from Gherkin feature files. | ||
``` | ||
docker compose run --rm ai sh /app/ai_scripts/regenerate_steps.sh | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import argparse | ||
import os | ||
import openai | ||
from pyppeteer import launch | ||
import re | ||
import asyncio | ||
from dotenv import load_dotenv | ||
|
||
# Load environment variables from .env file | ||
load_dotenv() | ||
|
||
GPT_ENDPOINT = os.getenv("GPT_ENDPOINT") | ||
SUBSCRIPTION_KEY = os.getenv("SUBSCRIPTION_KEY") | ||
|
||
# Initialize the OpenAI client with custom endpoint and subscription key | ||
openai.api_base = GPT_ENDPOINT | ||
openai.api_key = SUBSCRIPTION_KEY | ||
|
||
# Function to scrape and update selectors | ||
async def scrape_and_update_selectors(): | ||
browser = await launch() | ||
page = await browser.newPage() | ||
await page.goto('http://localhost:8888/login') | ||
|
||
# Scrape a selector | ||
username_selector = await page.evaluate("() => document.querySelector('input[name=\"username\"]').selectorText") | ||
password_selector = await page.evaluate("() => document.querySelector('input[name=\"password\"]').selectorText") | ||
|
||
await browser.close() | ||
|
||
# Update Utility.js | ||
auth_js_path = '/e2e/cypress/support/utility-functions/auth.js' | ||
|
||
with open(auth_js_path, 'w') as file: | ||
file.write(f""" | ||
export const login = () => {{ | ||
cy.visit(Cypress.env('baseUrl') + '/login'); | ||
cy.get('{username_selector}').type(Cypress.env('USERNAME')); | ||
cy.get('{password_selector}').type(Cypress.env('PASSWORD')); | ||
cy.get('button[type="submit"]').click(); | ||
}}; | ||
""") | ||
|
||
# Function to rename functions to be descriptive | ||
def update_function_names(): | ||
auth_js_path = '/e2e/cypress/support/utility-functions/auth.js' | ||
|
||
with open(auth_js_path, 'r') as file: | ||
content = file.read() | ||
|
||
content = re.sub(r'const login =', 'const userLoginWithValidCredentials =', content) | ||
|
||
with open(auth_js_path, 'w') as file: | ||
file.write(content) | ||
|
||
# Function to generate step definitions from Gherkin | ||
def generate_step_definitions(): | ||
for root, dirs, files in os.walk('/e2e/cypress/integration'): | ||
for file in files: | ||
if file.endswith('.feature'): | ||
filepath = os.path.join(root, file) | ||
with open(filepath, 'r') as feature_file: | ||
steps = [line.strip() for line in feature_file if line.startswith(('Given', 'When', 'Then', 'And'))] | ||
|
||
step_definitions = "" | ||
for step in steps: | ||
# Use OpenAI to generate step definitions | ||
response = openai.Completion.create( | ||
engine="davinci-codex", | ||
prompt=f"Generate a step definition for '{step}':", | ||
max_tokens=150 | ||
) | ||
step_definitions += response.choices[0].text.strip() + "\n" | ||
|
||
with open(filepath.replace('.feature', '_steps.js'), 'w') as step_file: | ||
step_file.write(step_definitions) | ||
|
||
def main(): | ||
parser = argparse.ArgumentParser() | ||
parser.add_argument('--task', required=True, help="Specify the task to run: regenerate-steps, update-utilities") | ||
args = parser.parse_args() | ||
|
||
if args.task == 'regenerate-steps': | ||
generate_step_definitions() | ||
elif args.task == 'update-utilities': | ||
asyncio.get_event_loop().run_until_complete(scrape_and_update_selectors()) | ||
update_function_names() | ||
else: | ||
print(f"Unknown task: {args.task}") | ||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#!/bin/sh | ||
|
||
echo "Starting regenerate_steps.sh in Docker container..." | ||
|
||
# Navigate to the script directory | ||
cd /app/ai_scripts | ||
|
||
# Run your Python script | ||
# python3 ai_worker.py --task regenerate_steps | ||
|
||
echo "Finished regenerate_steps.sh" |
Oops, something went wrong.