Skip to content

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=local works locally
  • pnpm lint passes 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

  1. Install dependencies (@playwright/test, playwright-bdd)
  2. Create acceptance test project in apps/acceptance-tests
  3. Create project.json with Nx targets
  4. Create Playwright configuration
  5. Create environment configuration
  6. Create Gherkin feature files
  7. Create step definition files
  8. Create support files (world, hooks)
  9. Test locally against running staging environment
  10. Update Azure Pipeline with acceptance test stage
  11. Run full pipeline to verify integration
  12. Update README.md with acceptance testing documentation
  13. 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

  1. Push to develop branch
  2. Pipeline should run: Validate → Test → Build → Package → Deploy Staging → Acceptance Tests
  3. Acceptance test results visible in Azure DevOps Test tab
  4. 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.