Promo Codes Testing Best Practices (2026)
Promo Codes Testing Best Practices (2026) start with a clear definition of what a promo code is and why its validation matters to revenue and user trust. A promo code is a string that, when applied du
Promo Codes Testing Best Practices (2026) start with a clear definition of what a promo code is and why its validation matters to revenue and user trust. A promo code is a string that, when applied during checkout or account creation, triggers a discount, free item, or other benefit governed by a set of business rules. Those rules can include eligibility based on user segment, geography, purchase amount, product category, expiration date, usage limits, and whether the code can be combined with other offers. If any of those rules are mis‑implemented, the company can lose money through over‑discounting, frustrate legitimate users with false rejections, or open the door to fraud and abuse. In 2026, the pressure to ship frequent promotional campaigns while maintaining strict fiscal controls has made promo‑code testing a critical quality gate. This guide walks you through the principles, a prioritized checklist, what to automate versus test manually, the failure modes that repeatedly appear in production, metrics that matter, tooling choices, CI/CD integration, and anti‑patterns to avoid. Concrete examples, two markdown tables, and code/command snippets are provided so you can copy‑paste them into your own repo.
1. Foundations: What to Test
Promo‑code validation touches several layers of an application, and each layer introduces distinct test concerns.
1.1 Business Rules Matrix
A good starting point is to enumerate the dimensions that affect whether a code should be accepted. The table below shows a typical matrix; you can extend it with domain‑specific attributes such as loyalty tier or subscription status.
| Dimension | Possible Values | Test Goal |
|---|---|---|
| Code format | alphanumeric, case‑sensitive, length 6‑12 | Verify parser accepts valid patterns and rejects invalid ones |
| Eligibility | new user, returning user, region US/EU, age | Ensure only allowed segments can apply the code |
| Purchase constraints | min cart value, max cart value, SKU whitelist | Confirm discount applies only when cart meets criteria |
| Expiration | date‑time, relative (e.g., 7 days from issue) | Check that expired codes are rejected and future‑dated codes are not usable early |
| Usage limits | per‑user, per‑code, global cap | Enforce that limits are respected after each successful redemption |
| Stackability | none, stackable with sale, stackable with other promo | Test that combining codes follows the defined policy (or is blocked) |
| Redemption flow | checkout page, in‑app purchase, API endpoint | Validate that the discount appears correctly in the UI and in the order total |
| Security | code leakage, brute‑force, replay | Ensure that guessing or reusing a code does not bypass limits |
1.2 User Personas Impact
Different user behaviors affect how promo codes are exercised. A curious user might try many codes in rapid succession; an impatient user may abandon the checkout if the code field is slow to validate; an adversarial user will attempt to exploit race conditions. When you design tests, map each persona to the relevant test scenarios:
- Curious – tries every code from a public list, expects clear error messages for invalid entries.
- Impatient – expects inline validation (e.g., real‑time format check) without a full round‑trip to the server.
- Novice – needs clear UI hints about where to enter the code and what format is expected.
- Adversarial – attempts to submit the same code many times concurrently, tries to modify request payloads to bypass usage limits.
- Elderly / Accessibility – relies on screen‑reader labels, sufficient contrast, and keyboard navigation to reach the code field.
- Power user – may paste a code from a clipboard, expects the field to trim whitespace automatically.
- Accessibility (WCAG) – verifies that error messages are announced, that the field is focusable, and that color is not the sole indicator of validity.
Understanding these personas helps you prioritize which test cases are automated (e.g., format validation) and which benefit from exploratory, persona‑driven testing (e.g., UI friction for an elderly user).
2. Test Design Principles
Effective promo‑code testing rests on a few core principles that keep the suite reliable, fast, and informative.
2.1 Determinism and Idempotency
Every test should produce the same outcome given the same inputs, regardless of when it runs. Promo‑code systems often rely on mutable state such as usage counters or expiration timestamps. To achieve determinism:
- Reset state before each test – truncate usage tables, reset counters, or spin up a fresh database snapshot.
- Use deterministic timestamps – mock
Date.now()or inject a clock interface so that expiration logic can be tested with fixed dates. - Make API calls idempotent – include a unique request identifier (e.g., UUID) that the backend uses to avoid double‑counting when retries happen.
2.2 State Isolation
Promo‑code validation frequently touches shared caches (e.g., Redis for usage counters) and external services (e.g., fraud detection). Isolate these dependencies:
- Containerized test environments – spin up a Redis instance per test suite via Docker Compose.
- Service virtualization – use tools like WireMock or Mountebank to simulate fraud‑service responses.
- Feature‑flag toggles – turn off external integrations in test mode and replace them with in‑process stubs.
2.3 Data‑Driven and Combinatorial Approaches
The business‑rules matrix naturally lends itself to data‑driven testing. Each row in the matrix becomes a test case; columns become parameters. For combinatorial coverage (e.g., testing interactions between eligibility and stackability), use pairwise or orthogonal array techniques to keep the number of tests manageable while still catching interaction bugs.
2.4 Negative and Boundary Testing
Positive paths (valid code, eligible user) are only half the story. Allocate at least 40 % of your test effort to:
- Invalid formats – wrong length, special characters, spaces.
- Expired / not‑yet‑valid – timestamps just before/after the boundary.
- Limit breaches – attempt to use a code one time beyond its per‑user cap.
- Incorrect segments – try a region‑restricted code from a disallowed locale.
- Stackability violations – apply a non‑stackable code together with a sale discount and verify the system blocks or applies the correct precedence.
2.5 Observability Hooks
Instrument the promo‑code service to emit structured logs and metrics each time a code is validated, accepted, or rejected. Tests can then assert on these observability signals (e.g., a Prometheus counter increment) rather than relying solely on UI text, making the suite less brittle to copy changes.
3. Manual Testing Checklist
Even with strong automation, certain aspects of promo‑code behavior benefit from human intuition, especially when evaluating UI/UX, edge‑case discovery, and abuse vectors. Below is a checklist you can run before a major promotional launch.
| Category | Manual Test Idea | Expected Outcome |
|---|---|---|
| UI Placement | Verify the promo‑code field is visible on cart, checkout, and account‑upgrade pages without scrolling. | Field is immediately reachable via Tab order and screen‑reader announcement. |
| Inline Validation | Type an invalid code; check that error appears instantly without form submission. | Inline message appears; server is not called. |
| Copy‑Paste Handling | Paste a code with leading/trailing spaces; observe trimming. | Spaces are removed; code is processed correctly. |
| Case Sensitivity | Try a code in upper‑case, lower‑case, mixed case per spec. | Only the case defined by the business rule is accepted. |
| Expiration Edge | Attempt to use a code exactly at its expiration second (e.g., 23:59:59). | System accepts if still within validity; rejects one second later. |
| Usage Limit Exhaustion | Redeem a per‑user‑limit‑2 code twice, then try a third time. | Third attempt rejected with clear limit‑exceeded message. |
| Stackability Conflict | Add a non‑stackable promo and a site‑wide sale; apply both. | Only the higher discount applies, or system shows an incompatibility warning. |
| Regional Block | Change device locale or IP to a disallowed country; try a US‑only code. | Code is rejected with a geo‑restriction message. |
| Accessibility | Navigate to the field using only keyboard; ensure error messages are read aloud. | Focus reaches field; screen reader announces invalid‑code message. |
| Performance Under Load | Open 20 browser tabs, each attempting to apply the same limited‑use code rapidly. | Backend throttles or returns 429; no more than the allowed number succeed. |
| Fraud Simulation | Use a script to generate random 8‑character strings at 10 req/s for 5 minutes. | Rate‑limiting or CAPTCHA triggers; no valid codes are guessed. |
| Error Message Clarity | Trigger each failure mode and verify the message tells the user what to do next. | Messages are actionable (e.g., “Code expired. Check your email for a new one.”). |
| Localization | Switch UI language; ensure promo‑code prompts and errors translate correctly. | All text appears in the selected language, placeholders preserved. |
| Fallback When Service Down | Simulate promo‑service downtime (e.g., block network); attempt to apply a code. | Graceful degradation: show a generic “Unable to validate promo” message and allow checkout to proceed without discount. |
Running this checklist manually before a release catches issues that automated scripts might miss, such as confusing wording, layout shifts on different screen sizes, or subtle accessibility gaps.
4. Automation Strategy
Deciding what to automate hinges on stability, speed, and risk. The following layers form a balanced automation pyramid for promo‑code testing.
4.1 Unit Tests for Validation Logic
At the base, unit tests cover pure functions that determine code eligibility, apply discounts, and enforce limits. These tests run in milliseconds and provide immediate feedback.
Example (Python, using pytest):
# promo_service.py
def is_eligible(code: str, user: User, cart: Cart, now: datetime) -> bool:
promo = PROMO_DB.get(code)
if not promo:
return False
if now < promo.start or now > promo.end:
return False
if promo.region and user.region != promo.region:
return False
if promo.min_cart and cart.total < promo.min_cart:
return False
if promo.usage_per_user and user.used_counts.get(code, 0) >= promo.usage_per_user:
return False
return True
def apply_discount(code: str, cart: Cart) -> Decimal:
promo = PROMO_DB.get(code)
if not promo:
return Decimal('0')
return cart.total * (promo.discount_pct / 100)
Unit test file:
# test_promo_service.py
import pytest
from datetime import datetime, timedelta
from promo_service import is_eligible, apply_discount
from models import User, Cart
@pytest.fixture
def base_user():
return User(id=1, region="US", used_counts={}, region="US")
def test_eligible_new_user():
user = base_user()
cart = Cart(total=Decimal('100'))
promo_code = "SUMMER20"
PROMO_DB[promo_code] = {
"start": datetime.utcnow() - timedelta(days=1),
"end": datetime.utcnow() + timedelta(days=30),
"region": "US",
"min_cart": 50,
"discount_pct": 20,
"usage_per_user": 1,
}
assert is_eligible(promo_code, user, cart, datetime.utcnow()) is True
assert apply_discount(promo_code, cart) == Decimal('20')
def test_ineligible_due_to_min_cart():
user = base_user()
cart = Cart(total=Decimal('30')) # below min_cart
promo_code = "SUMMER20"
PROMO_DB[promo_code] = {
"start": datetime.utcnow() - timedelta(days=1),
"end": datetime.utcnow() + timedelta(days=30),
"region": "US",
"min_cart": 50,
"discount_pct": 20,
"usage_per_user": 1,
}
assert is_eligible(promo_code, user, cart, datetime.utcnow()) is False
These tests exercise every branch of the eligibility function with minimal setup.
4.2 API Contract Tests
Promo‑code validation is often exposed via a REST or GraphQL endpoint (e.g., POST /v1/promo/validate). Contract tests ensure that the service’s request/response schema stays consistent across versions, protecting frontend teams from breaking changes.
Using Pact (Python) as an example:
# test_promo_contract.py
import pytest
from pact import Consumer, Provider
@pytest.fixture
def pact():
return Consumer('FrontendApp').has_pact_with(Provider('PromoService'), host_name='127.0.0.1', port=8000)
def test_validate_promo_success(pact):
expected = {
"code": "WELCOME10",
"valid": True,
"discount": 10.0,
"message": "Code applied successfully"
}
(pact
.given('Promo WELCOME10 exists and is unused')
.upon_receiving('a request to validate WELCOME10')
.with_request('POST', '/v1/promo/validate',
body={"code": "WELCOME10"})
.will_respond_with(200, body=expected))
with pact:
result = requests.post('http://127.0.0.1:8000/v1/promo/validate',
json={"code": "WELCOME10"})
assert result.json() == expected
Running this in CI catches drift in field names, data types, or HTTP status codes before they reach production.
4.3 End‑to‑End UI Tests
UI tests validate that the promo‑code field integrates correctly with the checkout flow, that discounts appear in the order summary, and that error messages are shown. Choose a tool that matches your stack: Appium for native Android/iOS, Playwright for web, or Cypress if you are already invested.
Playwright example (TypeScript):
// promo-code.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Promo code flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://shop.example.com/cart');
await page.fill('#cart-quantity-input', '2');
await page.click('#checkout-button');
});
test('applies a valid promo and shows discount', async ({ page }) => {
await page.fill('#promo-input', 'SPRING15');
await page.click('#apply-promo-btn');
const discountText = await page.locator('#discount-amount').innerText();
expect(discountText).toBe('-$15.00');
const totalText = await page.locator('#order-total').innerText();
expect(totalText).toBe('$85.00'); // assuming $100 cart - 15%
});
test('shows inline error for expired code', async ({ page }) => {
await page.fill('#promo-input', 'OLD20');
await page.click('#apply-promo-btn');
const error = await page.locator('#promo-error').innerText();
expect(error).toBe('This code has expired.');
});
test('prevents stacking non‑stackable promo', async ({ page }) => {
await page.fill('#promo-input', 'NONSTACK');
await page.click('#apply-promo-btn');
await page.fill('#promo-input', 'SALE10'); // site‑wide sale already applied
await page.click('#apply-promo-btn');
const warning = await page.locator('#promo-warning').innerText();
expect(warning).toBe('Cannot combine with other promotions.');
});
});
These tests give confidence that the entire user journey—from entering the code to seeing the final price—works as intended.
4.4 Performance and Load Tests
Promo‑code validation can become a bottleneck during flash sales. Use a tool like k6 or Gatling to simulate thousands of validation requests per second, asserting on latency and error rates.
k6 script snippet:
import http from 'k6/http';
import { check, sleep } from 'k6';
import { Counter } from 'k6/metrics';
const errorCounter = new Counter('promo_validation_errors');
export const options = {
vus: 200,
duration: '2m',
};
export default function () {
const payload = JSON.stringify({ code: 'FLASH50' });
const params = { headers: { 'Content-Type': 'application/json' } };
const res = http.post('https://api.example.com/v1/promo/validate', payload, params);
const ok = check(res, {
'status is 200': (r) => r.status === 200,
'valid true': (r) => r.json().valid === true,
});
if (!ok) errorCounter.add(1);
sleep(0.5);
}
Run this in a staging environment that mirrors production traffic patterns; adjust VUS and duration based on expected peak load.
4.5 Using SUSATest for Persona‑Driven Exploration
SUSATest can autonomously exercise promo‑code flows with different user personalities, surfacing issues that scripted tests might miss. After uploading your APK or providing a web URL, you can enable the “promo‑code” persona pack (curious, impatient, adversarial, etc.) and let the agent explore.
CLI example:
# Install the agent (once)
pip install susatest-agent
# Run a 15‑minute exploration on a staging web app
susatest explore \
--url https://staging.shop.example.com \
--personas curious impatient adversarial \
--duration 15m \
--output ./susareport.json \
--tags promo-code
The resulting report includes:
- Number of distinct promo‑code attempts per persona.
- Any crashes, ANRs, or validation errors encountered.
- Detected UI friction (e.g., the promo field missing a label for screen‑reader users).
- Security findings such as rate‑limit bypass attempts.
Integrating SUSATest into a nightly job gives you continuous, persona‑rich feedback without writing additional test code.
5. Metrics, Coverage, and Reporting
Testing effectiveness is measured not just by pass/fail counts but by how well the suite guards against revenue loss and user frustration.
5.1 Test Coverage Dimensions
| Coverage Type | What to Measure | Target (2026) |
|---|---|---|
| Code (line/branch) | Promo‑service unit test coverage | ≥ 90 % lines, ≥ 80 % branches |
| Business‑rule matrix | % of matrix cells exercised by at least one test | 100 % |
| Persona coverage | % of defined personas that have exercised a promo flow in exploratory runs | ≥ 80 % |
| Mutational (fuzz) | % of invalid inputs caught by negative tests | ≥ 95 % |
| Observability | % of validation events that emit a log/metric | 100 % |
| Production defect leakage | Bugs found in promo‑code logic per release | ≤ 0.5 per release |
Track these metrics in a dashboard (Grafana, Datadog, or similar) and set alerts when any dip below the threshold.
5.2 Key Performance Indicators (KPIs)
| KPI | Definition | Desired Trend |
|---|---|---|
| Mean Time to Detect (MTTD) | Average time from promo‑code defect introduction to detection in CI | Decrease |
| Mean Time to Resolve (MTTR) | Average time to fix a promo‑code bug after detection | Decrease |
| Escape Rate | Number of promo‑code defects reaching production per month | Approach zero |
| Discount Variance | Difference between expected discount sum (based on rules) and actual sum paid in production | Within ±0.1 % |
| Abuse Attempt Rate | Count of detected fraudulent promo‑code attempts per 1 k validation requests | Decrease with rate‑limiting improvements |
| Test Execution Time (suite) | Total time to run the full promo‑code test suite on CI | ≤ 8 min for feedback loop |
5.3 Reporting Practices
- JUnit XML for CI integration (most systems ingest this natively).
- Allure or ReportPortal for rich HTML reports with screenshots, logs, and metrics.
- Badge in README showing current line‑coverage and MTTD.
- Post‑run Slack notification summarizing any new failures, especially those tied to business‑rule matrix cells that moved from PASS to FAIL.
6. Tooling & Infrastructure
A solid testing ecosystem reduces flakiness and speeds up iteration.
6.1 Test Data Management
Promo‑code tests often need fresh codes, usage counters, and user profiles. Adopt a strategy:
- Ephemeral test tenants – spin up a separate database schema or namespace for each CI run.
- Deterministic seed data – load a known set of promo codes with predefined attributes via migration scripts.
- Data masking – if you must use a copy of production data, scrub PII and replace promo codes with synthetic ones that follow the same format rules.
6.2 Mock Servers & Service Virtualization
When the promo‑code service depends on external fraud‑scoring or inventory systems, replace them with mocks:
- WireMock – define stubs that return specific JSON based on request headers (e.g., simulate a fraud‑score of 0.2 vs 0.9).
- Mountebank – support for multiple protocols (HTTP, TCP) and easy injection of latency to test timeout handling.
- Contract‑first approach – write the Pact contract first, then implement the stub; this guarantees that both consumer and provider evolve together.
6.3 Feature Flags & Canary Releases
Promo campaigns are frequently toggled via feature flags. Test both the flag‑on and flag‑off states:
- LaunchDarkly / ConfigCat – use their SDKs to override flag values in test environments.
- Canary validation – deploy the promo‑code changes to a small percentage of real users, monitor the discount variance KPI, and roll back if it deviates.
6.4 Integrating SUSATest into the Pipeline
Because SUSATest generates executable regression scripts (Appium for Android, Playwright for Web), you can treat its output as part of your automated suite:
# .github/workflows/susatest.yml
name: SUSATest Exploration
on:
schedule:
- cron: '0 2 * * *' # nightly at 02:00 UTC
workflow_dispatch:
jobs:
explore:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install SUSATest agent
run: pip install susatest-agent
- name: Run exploration
env:
SUSA_API_KEY: ${{ secrets.SUSA_API_KEY }}
run: |
susatest explore \
--url https://staging.shop.example.com \
--personas curious impatient adversarial elderly \
--duration 20m \
--output susa-report.json \
--format junit \
--junit-output susa-results.xml
- name: Publish JUnit results
if: always()
uses: actions/upload-artifact@v3
with:
name: susa-junit
path: susa-results.xml
The generated JUnit file can be consumed by your CI to gate merges if any new failures appear.
7. CI/CD Integration
Embedding promo‑code tests into your delivery pipeline ensures that regressions are caught early.
7.1 Pipeline Stages
- Static analysis – lint promo‑code related code, check for hard‑coded strings.
- Unit test – run the pytest/jest suite; fail on < 90 % line coverage.
- Contract test – execute Pact verification; break if provider changes break consumer expectations.
- API smoke – hit the staging promo endpoint with a few known good/bad codes; validate response schema.
- Exploratory run (optional) – trigger SUSATest for a short window; treat any new crash or ANR as a failure.
- End‑to‑end UI – run Playwright/Appium suite in a headless browser/device farm.
- Performance check – execute a brief k6 load test; assert latency < 200 ms at 100 VUs.
- Deploy to canary – if all previous stages pass, roll out to 5 % traffic; monitor KPIs for 10 min.
- Promote to full – if canary metrics are healthy, promote to 100 %.
7.2 Example GitHub Actions Workflow
name: Promo Code CI
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: promo_test
ports: [5432:5432]
options: >-
--health-cmd "pg_isready -U test -d promo_test"
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports: [6379:6379]
steps:
- uses: actions/checkout@v3
- name: Set up Node
uses: actions/setup-node@v3
with:
node-version: '20'
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
npm ci
pip install -r requirements.txt
pip install pytest pytest-mock
- name: Run unit tests
run: pytest --cov=promo_service --cov-report=xml --cov-fail-under=90
- name: Run contract tests
run: pytest tests/contract/
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run UI tests
run: npx playwright test
- name: Run exploratory SUSATest (nightly only)
if: github.event_name == 'schedule'
env:
SUSA_API_KEY: ${{ secrets.SUSA_API_KEY }}
run: |
pip install susatest-agent
susatest explore \
--url https://staging.shop.example.com \
--personas curious impatient \
--duration 10m \
--output susa-report.json \
--format junit \
--junit-output susa-results.xml
# treat any new failures as a failure for the workflow
if [ -f susa-results.xml ] && grep -q '<failure' susa-results.xml; then exit 1; fi
- name: Run performance smoke
run: |
npm install -g k6
k6 run --vus 50 --duration 30s perf/promo_load_test.js
- name: Notify Slack on failure
if: failure()
uses: slackapi/slack-github-action@v1.23.0
with:
payload: |
{
"text": ":rotating_light: Promo‑code CI failed on ${{ github.repository }}",
"attachments": [
{
"color": "danger",
"fields": [
{ "title": "Workflow", "value": "${{ github.workflow }}", "short": true },
{ "title": "Commit", "value": "${{ github.sha }}", "short": true }
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
This workflow gives you fast feedback on unit and contract levels, moderate‑speed UI validation, and a nightly deep‑dive with SUSATest. Adjust timing and resource allocation to match your team’s capacity.
8. Failure Modes Observed in Production
Even with rigorous testing, certain promo‑code defects repeatedly surface in live environments. Knowing them helps you focus your test effort.
8.1 Race Conditions on Usage Limits
Scenario: Two concurrent requests (e.g., from a mobile app and a web tab) both read the usage counter as zero, increment it, and persist, resulting in the limit being exceeded by one.
Symptoms: Users report that a “single‑use” code worked twice; finance sees unexpected discount spend.
Mitigation: Use atomic operations (e.g., Redis INCR with a Lua script, or database UPDATE … SET used = used + 1 WHERE code = ? AND used < limit RETURNING used). Write a concurrency test that fires dozens of parallel requests and asserts the final counter never exceeds the limit.
8.2 Expired Codes Still Accepted Due to Clock Skew
Scenario: A promo code’s expiration is stored in UTC, but the application server runs in a local timezone without proper conversion. During daylight‑saving transitions, the code appears valid for an extra hour.
Symptoms: Spike in redemptions shortly after the expected expiry; audit logs show timestamps off by an hour.
Mitigation: Enforce timezone‑aware DateTime objects everywhere; add unit tests that simulate clock changes; monitor the “expired‑but‑accepted” metric in production.
8.3 Regional Bypass via Header Manipulation
Scenario: A promo code limited to the EU is accepted when the request includes a custom X-Forwarded-For header set to an EU IP, while the actual client IP is elsewhere.
Symptoms: Fraudulent redemptions from unexpected geographies; increase in chargeback rates.
Mitigation: Validate geography using a trusted source (e.g., MaxMind DB) and ignore client‑supplied headers for location decisions. Add negative tests that attempt to spoof the header.
8.4 Stackability Logic Errors
Scenario: A “free shipping” promo and a “10 % off” promo are both marked as non‑stackable, yet the checkout applies both because the logic only checks the first promo found.
Symptoms: Customers receive a larger discount than intended; margin erosion.
Mitigation: Centralize stackability decisions in a service that evaluates all applicable promos and returns a single combined discount according to policy. Write combinatorial tests covering all permutations of two‑promo sets.
###
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free