AI Prompt: Forma3D.Connect — Phase 1d: Acceptance Testing ⏳¶
Purpose: This prompt instructs an AI to implement Phase 1d of Forma3D.Connect
Estimated Effort: 8 hours
Prerequisites: Phase 1c completed (Staging deployment with Docker, Traefik, DigitalOcean)
Output: Automated acceptance testing with Playwright and Gherkin, integrated into Azure DevOps pipeline
Status: ⏳ PENDING
🎯 Mission¶
You are continuing development of Forma3D.Connect, building on the Phase 1c foundation. Your task is to implement Phase 1d: Acceptance Testing — establishing automated acceptance tests that run against the deployed staging environment.
Phase 1d delivers:
- Acceptance test project using Playwright with Gherkin syntax
- Given/When/Then step definitions for readable test scenarios
- Azure DevOps pipeline stage for post-deployment testing
- Test reporting with JUnit/HTML output visible in Azure DevOps
📋 Phase 1d Context¶
What Was Built in Phase 0, 1, 1b & 1c¶
The foundation is already in place:
- Nx monorepo with
apps/api,apps/web, and shared libs - PostgreSQL database with Prisma schema
- NestJS backend with Shopify webhooks, order storage, product mappings
- React 19 frontend with basic dashboard
- Azure DevOps CI/CD pipeline (validate, test, build, package, deploy stages)
- OpenAPI/Swagger documentation at
/api/docs - Aikido Security Platform for vulnerability scanning
- Sentry observability with error tracking and performance monitoring
- Structured JSON logging with correlation IDs
- Docker images deployed to DigitalOcean via Traefik
- Staging environment accessible at:
- API:
https://staging-connect-api.forma3d.be - Web:
https://staging-connect.forma3d.be
What Phase 1d Builds¶
| Feature | Description | Effort |
|---|---|---|
| F1d.1: Acceptance Test Project | Nx project with Playwright + Gherkin | 3 hours |
| F1d.2: Gherkin Feature Files | Given/When/Then acceptance scenarios | 2 hours |
| F1d.3: Playwright Step Defs | Step implementations using Playwright | 2 hours |
| F1d.4: Pipeline Integration | Azure DevOps acceptance test stage | 1 hour |
🛠️ Tech Stack Reference¶
All technologies from Phase 1c remain. Additional packages for Phase 1d:
| Package | Purpose |
|---|---|
@playwright/test |
E2E browser automation framework |
@cucumber/cucumber |
Gherkin parser and test runner |
playwright-bdd |
Playwright + Cucumber/Gherkin integration |
@cucumber/html-formatter |
HTML report generation |
📁 New Files to Create¶
Add to the existing structure:
apps/acceptance-tests/
├── src/
│ ├── features/
│ │ ├── health-check.feature # Basic health check scenarios
│ │ ├── api-docs.feature # Swagger documentation scenarios
│ │ └── web-dashboard.feature # Web UI smoke tests
│ ├── steps/
│ │ ├── health-check.steps.ts # Health check step definitions
│ │ ├── api-docs.steps.ts # API docs step definitions
│ │ └── web-dashboard.steps.ts # Dashboard step definitions
│ ├── support/
│ │ ├── world.ts # Cucumber World context
│ │ ├── hooks.ts # Before/After hooks
│ │ └── config.ts # Environment configuration
│ └── fixtures/
│ └── test-fixtures.ts # Playwright fixtures
├── playwright.config.ts # Playwright configuration
├── cucumber.js # Cucumber configuration
├── project.json # Nx project configuration
├── tsconfig.json # TypeScript configuration
├── tsconfig.spec.json # Test TypeScript configuration
└── README.md # Project documentation
🔧 Feature F1d.1: Acceptance Test Project¶
Requirements Reference¶
- NFR-AT-001: Acceptance Testing (new)
- NFR-AT-002: Gherkin Syntax (new)
- NFR-MA-002: Test Coverage
Implementation¶
1. Create Nx Project¶
Generate the acceptance tests project:
pnpm nx g @nx/js:library acceptance-tests --directory=apps/acceptance-tests --unitTestRunner=none --bundler=none
2. Project Configuration¶
Create apps/acceptance-tests/project.json:
{
"name": "acceptance-tests",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/acceptance-tests/src",
"projectType": "application",
"tags": ["type:e2e", "scope:acceptance"],
"targets": {
"test": {
"executor": "nx:run-commands",
"options": {
"command": "npx playwright test",
"cwd": "apps/acceptance-tests"
},
"configurations": {
"staging": {
"command": "STAGING_API_URL=https://staging-connect-api.forma3d.be STAGING_WEB_URL=https://staging-connect.forma3d.be npx playwright test"
},
"local": {
"command": "STAGING_API_URL=http://localhost:3000 STAGING_WEB_URL=http://localhost:4200 npx playwright test"
}
}
},
"test:headed": {
"executor": "nx:run-commands",
"options": {
"command": "npx playwright test --headed",
"cwd": "apps/acceptance-tests"
}
},
"report": {
"executor": "nx:run-commands",
"options": {
"command": "npx playwright show-report",
"cwd": "apps/acceptance-tests"
}
}
}
}
3. Playwright Configuration¶
Create apps/acceptance-tests/playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
import { defineBddConfig } from 'playwright-bdd';
const testDir = defineBddConfig({
features: 'src/features/**/*.feature',
steps: 'src/steps/**/*.steps.ts',
});
export default defineConfig({
testDir,
timeout: 30000,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['list'],
['html', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'test-results/junit.xml' }],
],
use: {
baseURL: process.env.STAGING_API_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
4. Environment Configuration¶
Create apps/acceptance-tests/src/support/config.ts:
/**
* Acceptance test environment configuration
* URLs are injected via environment variables for CI/CD flexibility
*/
export const config = {
apiBaseUrl: process.env.STAGING_API_URL || 'http://localhost:3000',
webBaseUrl: process.env.STAGING_WEB_URL || 'http://localhost:4200',
timeout: {
page: 30000,
api: 10000,
},
};
/**
* Validates that required environment configuration is present
*/
export function validateConfig(): void {
if (!config.apiBaseUrl) {
throw new Error('STAGING_API_URL environment variable is required');
}
if (!config.webBaseUrl) {
throw new Error('STAGING_WEB_URL environment variable is required');
}
}
🔧 Feature F1d.2: Gherkin Feature Files¶
Requirements Reference¶
- NFR-AT-002: Gherkin Syntax
- NFR-AT-003: Readable Test Scenarios
Implementation¶
1. Health Check Feature¶
Create apps/acceptance-tests/src/features/health-check.feature:
@smoke @api
Feature: Health Check Endpoints
As a DevOps engineer
I want to verify that the API is healthy
So that I can confirm successful deployment
Background:
Given the staging API is available
@critical
Scenario: API health endpoint returns OK
When I request the health endpoint
Then the response status should be 200
And the response should contain "status" with value "ok"
@critical
Scenario: API health endpoint reports database connectivity
When I request the health endpoint
Then the response should contain "database" with value "connected"
Scenario: API returns build information
When I request the health endpoint
Then the response should contain "timestamp"
2. API Documentation Feature¶
Create apps/acceptance-tests/src/features/api-docs.feature:
@smoke @api
Feature: API Documentation
As a developer
I want to access the API documentation
So that I can understand available endpoints
Background:
Given the staging API is available
@critical
Scenario: Swagger UI is accessible
When I navigate to the API documentation
Then the page should load successfully
And the page title should contain "Swagger"
Scenario: OpenAPI specification is available
When I request the OpenAPI JSON specification
Then the response status should be 200
And the response should be valid JSON
And the response should contain "openapi" with value "3.0.0"
3. Web Dashboard Feature¶
Create apps/acceptance-tests/src/features/web-dashboard.feature:
@smoke @web
Feature: Web Dashboard
As an operator
I want to access the web dashboard
So that I can monitor the system
Background:
Given the staging web application is available
@critical
Scenario: Dashboard home page loads
When I navigate to the dashboard home page
Then the page should load successfully
And the page should not display an error boundary
Scenario: Dashboard displays navigation
When I navigate to the dashboard home page
Then I should see the main navigation
🔧 Feature F1d.3: Playwright Step Definitions¶
Requirements Reference¶
- NFR-AT-001: Acceptance Testing
- NFR-AT-004: Playwright Integration
Implementation¶
1. Cucumber World Context¶
Create apps/acceptance-tests/src/support/world.ts:
import { setWorldConstructor, World, IWorldOptions } from '@cucumber/cucumber';
import { APIRequestContext, Page, request } from '@playwright/test';
import { config } from './config';
export interface AcceptanceWorld extends World {
page?: Page;
apiContext?: APIRequestContext;
response?: {
status: number;
body: unknown;
headers: Record<string, string>;
};
}
export class CustomWorld extends World implements AcceptanceWorld {
page?: Page;
apiContext?: APIRequestContext;
response?: {
status: number;
body: unknown;
headers: Record<string, string>;
};
constructor(options: IWorldOptions) {
super(options);
}
async initApiContext(): Promise<void> {
this.apiContext = await request.newContext({
baseURL: config.apiBaseUrl,
timeout: config.timeout.api,
});
}
async cleanup(): Promise<void> {
if (this.apiContext) {
await this.apiContext.dispose();
}
}
}
setWorldConstructor(CustomWorld);
2. Test Hooks¶
Create apps/acceptance-tests/src/support/hooks.ts:
import { Before, After, BeforeAll, AfterAll, Status } from '@cucumber/cucumber';
import { chromium, Browser, BrowserContext, Page } from '@playwright/test';
import { CustomWorld } from './world';
import { config, validateConfig } from './config';
let browser: Browser;
let context: BrowserContext;
BeforeAll(async function () {
validateConfig();
browser = await chromium.launch({
headless: process.env.HEADED !== 'true',
});
});
AfterAll(async function () {
await browser?.close();
});
Before(async function (this: CustomWorld) {
context = await browser.newContext({
baseURL: config.webBaseUrl,
});
this.page = await context.newPage();
await this.initApiContext();
});
After(async function (this: CustomWorld, scenario) {
if (scenario.result?.status === Status.FAILED && this.page) {
const screenshot = await this.page.screenshot();
this.attach(screenshot, 'image/png');
}
await context?.close();
await this.cleanup();
});
3. Health Check Steps¶
Create apps/acceptance-tests/src/steps/health-check.steps.ts:
import { Given, When, Then } from '@playwright/test';
import { expect } from '@playwright/test';
import { config } from '../support/config';
Given('the staging API is available', async ({ request }) => {
// Verify API is reachable
const response = await request.get(`${config.apiBaseUrl}/health`);
expect(response.ok()).toBeTruthy();
});
When('I request the health endpoint', async ({ request }, testInfo) => {
const response = await request.get(`${config.apiBaseUrl}/health`);
testInfo.attach('response', {
body: JSON.stringify({
status: response.status(),
body: await response.json(),
}),
contentType: 'application/json',
});
(testInfo as unknown as { response: unknown }).response = {
status: response.status(),
body: await response.json(),
};
});
Then('the response status should be {int}', async ({}, testInfo, expectedStatus: number) => {
const response = (testInfo as unknown as { response: { status: number } }).response;
expect(response.status).toBe(expectedStatus);
});
Then(
'the response should contain {string} with value {string}',
async ({}, testInfo, key: string, value: string) => {
const response = (testInfo as unknown as { response: { body: Record<string, unknown> } })
.response;
expect(response.body[key]).toBe(value);
}
);
Then('the response should contain {string}', async ({}, testInfo, key: string) => {
const response = (testInfo as unknown as { response: { body: Record<string, unknown> } })
.response;
expect(response.body).toHaveProperty(key);
});
4. API Documentation Steps¶
Create apps/acceptance-tests/src/steps/api-docs.steps.ts:
import { Given, When, Then } from '@playwright/test';
import { expect, Page } from '@playwright/test';
import { config } from '../support/config';
let page: Page;
let apiResponse: { status: number; body: unknown };
When('I navigate to the API documentation', async ({ page: testPage }) => {
page = testPage;
await page.goto(`${config.apiBaseUrl}/api/docs`);
});
When('I request the OpenAPI JSON specification', async ({ request }, testInfo) => {
const response = await request.get(`${config.apiBaseUrl}/api/docs-json`);
apiResponse = {
status: response.status(),
body: await response.json(),
};
(testInfo as unknown as { response: typeof apiResponse }).response = apiResponse;
});
Then('the page should load successfully', async () => {
await expect(page).toHaveURL(/.*docs.*/);
});
Then('the page title should contain {string}', async ({}, expectedText: string) => {
const title = await page.title();
expect(title.toLowerCase()).toContain(expectedText.toLowerCase());
});
Then('the response should be valid JSON', async ({}, testInfo) => {
const response = (testInfo as unknown as { response: typeof apiResponse }).response;
expect(response.body).toBeDefined();
expect(typeof response.body).toBe('object');
});
5. Web Dashboard Steps¶
Create apps/acceptance-tests/src/steps/web-dashboard.steps.ts:
import { Given, When, Then } from '@playwright/test';
import { expect, Page } from '@playwright/test';
import { config } from '../support/config';
let page: Page;
Given('the staging web application is available', async ({ request }) => {
const response = await request.get(`${config.webBaseUrl}/`);
expect(response.ok()).toBeTruthy();
});
When('I navigate to the dashboard home page', async ({ page: testPage }) => {
page = testPage;
await page.goto(config.webBaseUrl);
await page.waitForLoadState('networkidle');
});
Then('the page should not display an error boundary', async () => {
// Check for common error boundary indicators
const errorBoundary = page.locator('[data-testid="error-boundary"]');
await expect(errorBoundary).not.toBeVisible();
// Also check for "Something went wrong" text
const errorText = page.getByText('Something went wrong');
await expect(errorText).not.toBeVisible();
});
Then('I should see the main navigation', async () => {
// Look for navigation element
const nav = page.locator('nav, [role="navigation"]');
await expect(nav).toBeVisible();
});
🔧 Feature F1d.4: Azure Pipeline Integration¶
Requirements Reference¶
- NFR-AT-005: CI/CD Integration
- NFR-DE-007: Pipeline Integration
Implementation¶
Add the following stage to azure-pipelines.yml after the DeployStaging stage:
# --------------------------------------------------------------------------
# Stage: Acceptance Tests
# --------------------------------------------------------------------------
- stage: AcceptanceTests
displayName: 'Acceptance Tests'
dependsOn: DeployStaging
condition: succeeded()
jobs:
- job: RunAcceptanceTests
displayName: 'Run Acceptance Tests'
steps:
- task: NodeTool@0
displayName: 'Install Node.js'
inputs:
versionSpec: '$(nodeVersion)'
- script: |
corepack enable
corepack prepare pnpm@$(pnpmVersion) --activate
displayName: 'Install pnpm'
- script: pnpm install --frozen-lockfile
displayName: 'Install Dependencies'
- script: npx playwright install --with-deps chromium
displayName: 'Install Playwright Browsers'
- script: |
pnpm nx run acceptance-tests:test --configuration=staging
displayName: 'Run Acceptance Tests'
env:
STAGING_API_URL: 'https://staging-connect-api.forma3d.be'
STAGING_WEB_URL: 'https://staging-connect.forma3d.be'
continueOnError: false
- task: PublishTestResults@2
displayName: 'Publish Test Results'
condition: always()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: 'apps/acceptance-tests/test-results/junit.xml'
mergeTestResults: true
testRunTitle: 'Acceptance Tests'
- task: PublishBuildArtifacts@1
displayName: 'Publish Test Report'
condition: always()
inputs:
pathToPublish: 'apps/acceptance-tests/playwright-report'
artifactName: 'acceptance-test-report'
🧪 Testing Requirements¶
Test Coverage Requirements¶
Per requirements.md (NFR-MA-002):
- Unit Tests: > 80% coverage
- Integration Tests: All API integrations tested
- Acceptance Tests: Critical paths covered with Gherkin scenarios
Test Scenarios Required¶
| Category | Scenario | Priority |
|---|---|---|
| Health Check | API health endpoint returns OK | Critical |
| Health Check | Database connectivity verified | Critical |
| API Docs | Swagger UI accessible | Critical |
| API Docs | OpenAPI spec available | High |
| Web Dashboard | Home page loads | Critical |
| Web Dashboard | No error boundary displayed | Critical |
✅ Validation Checklist¶
Infrastructure¶
- Acceptance test project created in
apps/acceptance-tests - All new files compile without errors
-
pnpm nx run acceptance-tests:test --configuration=localworks locally -
pnpm lintpasses on all new files
Gherkin Features (F1d.2)¶
- Feature files use proper Gherkin syntax
- Scenarios are readable by non-developers
- Tags applied for filtering (@smoke, @critical, @api, @web)
- Background sections reduce duplication
Step Definitions (F1d.3)¶
- All Gherkin steps have corresponding implementations
- Steps use Playwright for browser interactions
- Steps use Playwright API context for REST calls
- Screenshots captured on failure
Pipeline Integration (F1d.4)¶
- Acceptance test stage runs after staging deployment
- Tests target deployed staging environment
- JUnit results published to Azure DevOps
- HTML report published as artifact
- Pipeline fails if acceptance tests fail
🚫 Constraints and Rules¶
MUST DO¶
- Use environment variables for base URLs (no hardcoded hosts)
- Make tests idempotent and CI-safe
- Avoid inter-test dependencies
- Write scenarios readable by non-developers
- Capture screenshots on test failure
- Publish test results to Azure DevOps UI
MUST NOT¶
- Hardcode staging URLs in test code
- Create tests that modify production data
- Skip HMAC verification for webhook tests
- Create flaky tests that depend on timing
- Use experimental or niche testing frameworks
- Add unnecessary abstraction layers
🎬 Execution Order¶
- Install dependencies (
@playwright/test,playwright-bdd) - Create acceptance test project in
apps/acceptance-tests - Create project.json with Nx targets
- Create Playwright configuration
- Create environment configuration
- Create Gherkin feature files
- Create step definition files
- Create support files (world, hooks)
- Test locally against running staging environment
- Update Azure Pipeline with acceptance test stage
- Run full pipeline to verify integration
- Update README.md with acceptance testing documentation
- Run full validation checklist
📊 Expected Output¶
When Phase 1d is complete:
Verification Commands¶
# Run acceptance tests locally (requires staging to be running)
pnpm nx run acceptance-tests:test --configuration=local
# Run against staging environment
pnpm nx run acceptance-tests:test --configuration=staging
# Run with browser visible
pnpm nx run acceptance-tests:test:headed
# View HTML report
pnpm nx run acceptance-tests:report
Pipeline Verification¶
- Push to
developbranch - Pipeline should run: Validate → Test → Build → Package → Deploy Staging → Acceptance Tests
- Acceptance test results visible in Azure DevOps Test tab
- HTML report available as pipeline artifact
Expected Test Output¶
✓ Health Check Endpoints
✓ API health endpoint returns OK
✓ API health endpoint reports database connectivity
✓ API returns build information
✓ API Documentation
✓ Swagger UI is accessible
✓ OpenAPI specification is available
✓ Web Dashboard
✓ Dashboard home page loads
✓ Dashboard displays navigation
🔗 Phase 1d Exit Criteria¶
- Acceptance test project created with Playwright + Gherkin
- Feature files written in Given/When/Then format
- Step definitions implemented with Playwright
- Tests pass against staging environment
- Azure DevOps pipeline includes acceptance test stage
- Test results published to Azure DevOps UI
- HTML report published as pipeline artifact
- Pipeline fails if acceptance tests fail
- Documentation updated in README.md
📝 Documentation Updates¶
README.md Updates Required¶
Add a new section for acceptance testing:
## Acceptance Testing
Acceptance tests verify that the deployed staging environment is functioning correctly.
### Running Acceptance Tests
```bash
# Run against local environment
pnpm nx run acceptance-tests:test --configuration=local
# Run against staging
pnpm nx run acceptance-tests:test --configuration=staging
# Run with browser visible (debugging)
pnpm nx run acceptance-tests:test:headed
# View HTML report after test run
pnpm nx run acceptance-tests:report
```
### Writing New Acceptance Tests
1. Create a `.feature` file in `apps/acceptance-tests/src/features/`
2. Write scenarios using Gherkin syntax (Given/When/Then)
3. Create step definitions in `apps/acceptance-tests/src/steps/`
4. Run tests locally to verify
### Test Tags
- `@smoke` — Quick smoke tests for deployment verification
- `@critical` — Critical path tests that must pass
- `@api` — API-only tests (no browser)
- `@web` — Web UI tests (requires browser)
END OF PROMPT
This prompt builds on the Phase 1c foundation. The AI should implement all Phase 1d acceptance testing features while maintaining the established code style, architectural patterns, and testing standards.