DEV Community

Cover image for Building an Autonomous E2E Test Generation Framework with Kiro CLI
Muhammad Awais for AWS Community Builders

Posted on • Originally published at Medium

Building an Autonomous E2E Test Generation Framework with Kiro CLI

How I Built an AIDLC E2E Test Case Generator Using .kiro Specs — From Feature Requirements to Passing Playwright Tests

Introduction

Testing web applications end-to-end is often tedious. You write tests, they break, you rewrite them. But what if you could follow a structured, AI-driven development lifecycle (AIDLC) that generates feature requirements from your actual deployed app, then derives test cases systematically from those requirements?

In this article, I’ll walk you through how I built a complete E2E testing pipeline for a Social Media Event Attendee Banner Generator app using the .kiro spec-driven approach — going from analyzing the live DOM to generating Feature Requirements (FRs), Test Specs (TSs), and finally passing Playwright tests.

The App Under Test

The app is a simple but functional web tool that lets users:

  • Upload a headshot image
  • Crop it to a square using Cropper.js
  • Enter their name, designation, and company
  • Generate a professional event banner on an HTML5 Canvas Auto-download it as a PNG
  • Tech Stack: HTML5, CSS3, Vanilla JS, jQuery, Cropper.js, Bootstrap 5, Canvas API

Deployed at: https://event-attendee-banner-generator.vercel.app/

The AIDLC Flow

Here’s the complete flow from app analysis to passing tests:

Figure 1. e2e aidlc flow

Step 1: Project Structure — The .kiro Way

Instead of writing tests directly, we first establish a spec-driven structure:

.kiro/
├── docs/
│   └── product-context.md          # App overview & tech stack
├── specs/
│   ├── feature-requirements/       # FR-1 through FR-7
│   │   ├── FR-1-page-load-and-layout.md
│   │   ├── FR-2-image-upload.md
│   │   ├── FR-3-image-cropping.md
│   │   ├── FR-4-attendee-info-form.md
│   │   ├── FR-5-banner-generation.md
│   │   ├── FR-6-canvas-banner-display.md
│   │   └── FR-7-usage-instructions.md
│   └── test-specs/                 # TS-1 through TS-7
│       ├── TS-1-page-load-and-layout.md
│       ├── TS-2-image-upload.md
│       ├── TS-3-image-cropping.md
│       ├── TS-4-attendee-info-form.md
│       ├── TS-5-banner-generation.md
│       ├── TS-6-canvas-banner-display.md
│       └── TS-7-usage-instructions.md
├── standards/
│   └── spec-standards.md           # Format rules for FRs & TSs
└── rules/
    └── project-rules.md            # Project conventions

e2e/
├── playwright.config.js
├── package.json
└── tests/                          # Playwright implementations
    ├── FR-1-page-load-and-layout.spec.js
    ├── FR-2-image-upload.spec.js
    └── ... (7 spec files)
Enter fullscreen mode Exit fullscreen mode

The key insight: specs are pure markdown, tests are derived from them, not the other way around.

Step 2: Product Context Document

First, we document what the app is:

# Social Media Event Attendee Banner Generator

## Overview
A web-based tool that allows users to create custom social media
banners for Tech Events. Users upload a headshot, enter their name,
designation, and company, and generate a professional banner optimized
for LinkedIn and Instagram sharing.

## Tech Stack
- HTML5, CSS3, Vanilla JavaScript
- jQuery 3.6.0
- Cropper.js 1.5.12 (image cropping)
- Bootstrap 5.3.3 (layout/styling)
- HTML5 Canvas API (banner rendering)

## Deployed URL
https://event-attendee-banner-generator.vercel.app/

## Key Files
- `index.html` — Main page with form and canvas
- `script.js` — App logic (upload, crop, generate, download)
- `style.css` — Custom styles
Enter fullscreen mode Exit fullscreen mode

Step 3: Feature Requirements (FRs)

Each feature gets its own markdown file with a consistent structure. Here’s an example:

FR-2: Image Upload

# FR-2: Image Upload

## Description
User can upload a headshot image by clicking the preview area,
which triggers a hidden file input.

## Acceptance Criteria
1. A default user icon (img/user-icon.png) is displayed as placeholder
2. Clicking the preview image triggers the hidden file input
   via onclick="imageUpload.click()"
3. File input (#imageUpload) has accept="image/*" and is hidden
   with class d-none
4. After selecting a file, a FileReader reads it as DataURL
5. The image-to-crop element (#imageToCrop) src is updated
6. The cropper container (#cropperContainer) becomes visible

## DOM Elements
- `#imagePreview` — Preview wrapper div
- `#previewImage` — img element showing placeholder/cropped result
- `#imageUpload` — Hidden file input (type="file", accept="image/*")
- `#cropperContainer` — Container shown after upload

## Priority
High
Enter fullscreen mode Exit fullscreen mode

FR-5: Banner Generation

# FR-5: Banner Generation

## Description
User clicks "Generate Banner" to render a composite banner on
the canvas with their headshot and details.

## Acceptance Criteria
1. "Generate Banner" button is visible with classes
   btn btn-warning w-100 mt-3
2. Button triggers generateBanner() via onclick attribute
3. If no cropped image exists, function returns early
4. Background image is drawn on canvas at full size
5. Cropped headshot is drawn as a circular clip at position x=60
6. User name is drawn in bold 50px white Arial text
7. Designation is drawn in normal 30px white Arial text
8. Company is drawn in normal 25px white Arial text
9. If inputs are empty, defaults are used
10. Banner auto-downloads as PNG

## DOM Elements
- `button[onclick="generateBanner()"]` — Generate Banner button
- `#downloadCanvas` — Canvas element for rendering

## Priority
High
Enter fullscreen mode Exit fullscreen mode

Step 4: Test Specs (TSs)

Each FR maps to a Test Spec with concrete, atomic test cases:

TS-2: Image Upload

# Test Spec: FR-2 — Image Upload

## Source
[FR-2-image-upload.md](../feature-requirements/FR-2-image-upload.md)

## Test Cases

### TC-2.1: Default user icon displayed
- **Action**: Check `#previewImage` src attribute
- **Expected**: src contains "user-icon.png"

### TC-2.2: Clicking preview triggers file input
- **Action**: Check `#previewImage` onclick attribute
- **Expected**: onclick contains "imageUpload.click()"

### TC-2.3: File input is hidden and accepts images
- **Action**: Check `#imageUpload` attributes
- **Expected**: Has class d-none, attribute accept="image/*"
Enter fullscreen mode Exit fullscreen mode

Step 5: Standards & Rules

We define how specs should be authored — this ensures consistency:

Spec Standards

## Feature Requirements (FR) Format
- File naming: `FR-{number}-{slug}.md`
- Required sections in order:
  1. `# FR-{n}: {Title}`
  2. `## Description`
  3. `## Acceptance Criteria` — Numbered list
  4. `## DOM Elements` — Relevant selectors
  5. `## Priority` — High | Medium | Low

## Test Specs (TS) Format
- File naming: `TS-{number}-{slug}.md` matching its FR
- Each test case must have:
  - `- **Action**:` — What to do
  - `- **Expected**:` — What to assert

## Coverage
- Every acceptance criterion must have at least one test case
Enter fullscreen mode Exit fullscreen mode

Project Rules

## General
- All spec artifacts live under `.kiro/specs/`
- No code-based generators — specs are pure markdown
- Deployed app URL is the source of truth for DOM verification

## Test Specs
- Test cases must be atomic — one assertion per test case
- Use semantic DOM selectors (#id preferred, then .class, then tag)
Enter fullscreen mode Exit fullscreen mode

Step 6: Playwright Test Implementation

Now we translate the markdown test specs into executable Playwright tests:

FR-1: Page Load & Layout

const { test, expect } = require('@playwright/test');

test.describe('FR-1: Page Load & Layout', () => {
  test('TC-1.1: Page title is correct', async ({ page }) => {
    await page.goto('/');
    await expect(page).toHaveTitle(
      'Social Media Sharing | Event Banner Generator | Marketing Events'
    );
  });

  test('TC-1.2: Main heading is displayed', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('h1')).toContainText(
      'Event Attending Poster Generator'
    );
    await expect(page.locator('h1 span.alert')).toContainText('#opensource');
  });

  test('TC-1.3: Two-column layout renders', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('.col-md-4')).toBeVisible();
    await expect(page.locator('.col-md-8')).toBeVisible();
  });

  test('TC-1.4: Bootstrap CSS is loaded', async ({ page }) => {
    await page.goto('/');
    await expect(
      page.locator('link[href*="bootstrap"]').first()
    ).toHaveAttribute('href', /bootstrap/);
  });

  test('TC-1.5: jQuery is loaded', async ({ page }) => {
    await page.goto('/');
    await expect(
      page.locator('script[src*="jquery"]')
    ).toHaveAttribute('src', /jquery/);
  });

  test('TC-1.6: Cropper.js is loaded', async ({ page }) => {
    await page.goto('/');
    await expect(
      page.locator('link[href*="cropperjs"]')
    ).toHaveAttribute('href', /cropper/);
    await expect(
      page.locator('script[src*="cropper.min.js"]')
    ).toHaveAttribute('src', /cropper/);
  });
});
Enter fullscreen mode Exit fullscreen mode

FR-4: Attendee Information Form

const { test, expect } = require('@playwright/test');

test.describe('FR-4: Attendee Information Form', () => {
  test('TC-4.1: Name input visible with placeholder', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('#nameInput')).toBeVisible();
    await expect(page.locator('#nameInput'))
      .toHaveAttribute('placeholder', 'Full Name');
  });

  test('TC-4.2: Designation input visible', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('#designationInput')).toBeVisible();
    await expect(page.locator('#designationInput'))
      .toHaveAttribute('placeholder', 'Designation');
  });

  test('TC-4.3: Company input visible', async ({ page }) => {
    await page.goto('/');
    await expect(page.locator('#companyInput')).toBeVisible();
    await expect(page.locator('#companyInput'))
      .toHaveAttribute('placeholder', 'Company');
  });

  test('TC-4.4: Inputs accept text entry', async ({ page }) => {
    await page.goto('/');
    await page.fill('#nameInput', 'John Doe');
    await page.fill('#designationInput', 'Engineer');
    await page.fill('#companyInput', 'Acme');
    await expect(page.locator('#nameInput')).toHaveValue('John Doe');
    await expect(page.locator('#designationInput')).toHaveValue('Engineer');
    await expect(page.locator('#companyInput')).toHaveValue('Acme');
  });
});
Enter fullscreen mode Exit fullscreen mode

Step 7: Playwright Configuration

The config serves the static app locally via http-server:

const { defineConfig } = require('@playwright/test');
const path = require('path');

module.exports = defineConfig({
  testDir: './tests',
  timeout: 30000,
  use: {
    baseURL: 'http://localhost:8080',
    headless: true,
  },
  webServer: {
    command: `npx http-server "${path.resolve(__dirname, '..')}" -p 8080 -s`,
    port: 8080,
    reuseExistingServer: true,
  },
});
Enter fullscreen mode Exit fullscreen mode

This means:

  • No separate server setup needed
  • Tests run against the actual app code
  • Works in CI/CD pipelines out of the box

Step 8: Running the Tests

cd e2e
npm install
npx playwright test
Enter fullscreen mode Exit fullscreen mode

Result

Running 22 tests using 4 workers

  ✓ FR-1: Page Load & Layout › TC-1.1: Page title is correct
  ✓ FR-1: Page Load & Layout › TC-1.2: Main heading is displayed
  ✓ FR-1: Page Load & Layout › TC-1.3: Two-column layout renders
  ✓ FR-1: Page Load & Layout › TC-1.4: Bootstrap CSS is loaded
  ✓ FR-1: Page Load & Layout › TC-1.5: jQuery is loaded
  ✓ FR-1: Page Load & Layout › TC-1.6: Cropper.js is loaded
  ✓ FR-2: Image Upload › TC-2.1: Default user icon displayed
  ✓ FR-2: Image Upload › TC-2.2: Clicking preview triggers file input
  ✓ FR-2: Image Upload › TC-2.3: File input is hidden and accepts images
  ✓ FR-3: Image Cropping › TC-3.1: Crop button exists
  ✓ FR-3: Image Cropping › TC-3.2: Cropper container initially hidden
  ✓ FR-4: Attendee Info Form › TC-4.1: Name input
  ✓ FR-4: Attendee Info Form › TC-4.2: Designation input
  ✓ FR-4: Attendee Info Form › TC-4.3: Company input
  ✓ FR-4: Attendee Info Form › TC-4.4: Inputs accept text
  ✓ FR-5: Banner Generation › TC-5.1: Generate button visible
  ✓ FR-6: Canvas Banner › TC-6.1: Canvas element exists
  ✓ FR-6: Canvas Banner › TC-6.2: Canvas dimensions 1200x630
  ✓ FR-7: Usage Instructions › TC-7.1: Info area visible
  ✓ FR-7: Usage Instructions › TC-7.2: Heading correct
  ✓ FR-7: Usage Instructions › TC-7.3: Instructions listed
  ✓ FR-7: Usage Instructions › TC-7.4: Hashtags displayed

  22 passed (5.5s)
Enter fullscreen mode Exit fullscreen mode

For headed mode with visual report:

npx playwright test --headed --reporter=html
npx playwright show-report
Enter fullscreen mode Exit fullscreen mode

The Traceability Matrix

Here’s how everything connects:

Figure 2. Playwright test traceability matrix

22 tests passed — e2e AIDLC

Why This Approach Works

  1. Requirements-First, Not Test-First
    By writing Feature Requirements before tests, you ensure you’re testing what matters — not just what’s easy to test.

  2. Full Traceability
    Every test maps back to an acceptance criterion, which maps to a feature. When a test fails, you know exactly which requirement is broken.

  3. Living Documentation
    The .kiro/specs/ folder serves as both documentation and test source. New team members can read the FRs to understand the app without running it.

  4. Spec-Driven, Not Code-Driven
    The markdown specs are the source of truth. Playwright code is just the implementation of those specs. If you switch test frameworks, the specs remain valid.

  5. AI-Friendly
    The structured format makes it trivial for AI agents (like Kiro) to:

  • Analyze a deployed app’s DOM
  • Generate FRs from what they observe
  • Derive test cases from the FRs
  • Implement the tests automatically

How to Adopt This in Your Project

  1. Create .kiro/docs/product-context.md — Document your app’s purpose and stack
  2. Analyze your app’s DOM — Visit it, identify interactive elements and flows
  3. Write Feature Requirements — One per feature, with numbered acceptance criteria
  4. Derive Test Specs — Map each criterion to an Action/Expected pair
  5. Define Standards — Set naming conventions and format rules
  6. Implement Tests— Translate TSs into your test framework of choice
  7. Run & Report — Use — headed mode for visual verification

The Kiro Prompts That Generated All This

Prompt 1: Initial Request

i want to develop an automated aidlc e2e test case generator for the 
current project by visiting the deployed url of the app and the codebase 
aidlc docs in a way that it first visits the app url and verify the 
input elements and DOM elements and page ui code and everything and 
reads the aidl-docs of the app and finally generates the feature 
requirements files in separate folder by naming FR-1-image preview 
like this, then generates the test cases spec files from FR files.
Enter fullscreen mode Exit fullscreen mode

Kiro analyzed the codebase, visited the deployed URL, and initially built a JavaScript-based generator. But I wanted it the .kiro way…

Prompt 2: Switch to .kiro Way

it should always use .kiro way not using javascript
Enter fullscreen mode Exit fullscreen mode

This single prompt made Kiro pivot entirely — removing the JS generator and creating pure markdown Feature Requirements and Test Specs following the .kiro spec-driven approach.

Prompt 3: Confirm Markdown-Only

yes use .kiro way only with md files and everything to make the context
Enter fullscreen mode Exit fullscreen mode

Kiro then generated all 7 FR files, 7 TS files, and the product context document — all as structured markdown.

Prompt 4: Add Standards & Rules

also create standard and rule files for the patterns we are following in this
Enter fullscreen mode Exit fullscreen mode

This produced the spec-standards.md and project-rules.md — codifying the naming conventions, format rules, and coverage requirements.

Prompt 5: Run Tests

now run e2e-aidlc and see test cases pass
Enter fullscreen mode Exit fullscreen mode

Kiro translated the markdown test specs into Playwright implementations, set up the test infrastructure, and ran them — 22 tests, all passing.

Conclusion

The AIDLC approach transforms testing from an afterthought into a structured, traceable process. By leveraging .kiro specs as the single source of truth, you get:

  • Clarity — Everyone knows what each feature should do
  • Coverage — Every requirement has a test
  • Maintainability — Change the spec, the test follows
  • Automation — AI can generate and update the entire pipeline
  • The result? 22 passing E2E tests derived systematically from 7 Feature Requirements, all backed by pure markdown documentation that anyone can read and understand.

The complete source code is available on GitHub.

https://github.com/muhammadawaisshaikh/social-media-event-attendee-banner-generator/pull/1

Top comments (0)