Skip to content

Add BDD tests for article content validation and enable test workflow #263

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Article Content Tests

This directory contains BDD tests for validating article content and layout.

## Test Files

### `article-content.feature`
Contains two scenarios testing the article at `/2025/07/08/08-comparing-anker-power-packs.html`:

1. **Article Content Validation**: Tests that the article has:
- Tags navigation with links to tag slugs
- Post header in H2 element
- Page title containing post title and "orionrobots"
- Visible images inside article tag without dead links
- Date and author in div element
- Footer with Discord and YouTube links
- Main navigation menu at top

2. **Desktop Layout Validation**: Tests that in desktop view:
- Images, tables and text don't overflow the article container margin

### `validate-article.sh`
A simple bash script that validates article structure without requiring Playwright. This is useful for:
- Quick validation during development
- CI environments where Playwright installation fails
- Manual testing

## Running Tests

### BDD Tests (Preferred)
```bash
# With local server
BASE_URL=http://localhost:8080 npm run test:bdd

# With Docker (includes staging server)
docker compose --profile manual run test
```

### Simple Validation (Fallback)
```bash
# Start a local server first
cd _site && python3 -m http.server 8080

# Run validation
./tests/validate-article.sh
```

## Prerequisites

### For BDD Tests
- Playwright browsers installed (`npx playwright install`)
- Site built and served (see project README)

### For Simple Validation
- `curl` command available
- Site served on localhost:8080

## Test Coverage

The tests validate that the article meets all requirements specified in issue #262:

- ✅ Set of tags in nav linking to tag slug places
- ✅ Post header in H2 element
- ✅ Page title contains post title and "orionrobots"
- ✅ Visible images inside article tag (not dead links)
- ✅ Date and author in div element
- ✅ Footer with Discord and YouTube links
- ✅ Main menu navigation at top
- ✅ Desktop layout doesn't overflow into sidebar

## Article Structure Verified

The target article `/2025/07/08/08-comparing-anker-power-packs.html` contains:

- Title: "Comparing anker power packs | Orionrobots - Learn to build robots at home"
- H2 header with `class="page-header"`
- 4 tag links: `/tags/robotics-at-home`, `/tags/robotics-projects`, `/tags/raspberry-pi`, `/tags/robot-building`
- 7 images within `<article>` tag with proper src attributes
- Date/author in `<div class="date text-secondary">` with `<time>` and author info
- Footer with Discord (`discord.gg`) and YouTube (`youtube.com/orionrobots`) links
- Main navigation in `<nav class="navbar">`
18 changes: 18 additions & 0 deletions tests/staging/features/article-content.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Feature: Article Content Tests

Scenario: Article has required content elements
Given the Staging site is started
When I navigate to the article "/2025/07/08/08-comparing-anker-power-packs.html"
Then the article should have a set of tags in a nav linking to tag slugs
And the article should have a post header in an H2 element
And the page title should contain the post title and "orionrobots"
And the article should have visible images inside the article tag
And the article should have a date and author in a div element
And the page should have a footer with Discord and YouTube links
And the page should have the main menu in a nav element at the top

Scenario: Desktop view layout does not overflow
Given the Staging site is started
When I navigate to the article "/2025/07/08/08-comparing-anker-power-packs.html"
And I am in desktop view
Then the images, tables and text should not overflow the article container
239 changes: 239 additions & 0 deletions tests/staging/step_definitions/website_steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,242 @@ Then('each recent post should have a picture tag with an img element', async fun
}
}
});

When('I navigate to the article {string}', async function (articlePath) {
if (!page) {
throw new Error('Page not initialized. Make sure "Given the Staging site is started" step is executed first.');
}

const fullUrl = BASE_URL + articlePath;
const response = await page.goto(fullUrl, {
waitUntil: 'networkidle',
timeout: 30000
});

if (!response || !response.ok()) {
throw new Error(`Article page failed to load. Status: ${response ? response.status() : 'No response'}`);
}
});

Then('the article should have a set of tags in a nav linking to tag slugs', async function () {
if (!page) {
throw new Error('Page not initialized. Make sure previous steps are executed first.');
}

try {
// Look for the tags navigation section
const tagsNav = await page.locator('nav.tag-row, nav:has(a[href*="/tags/"])').first();
await tagsNav.waitFor({ state: 'visible', timeout: 10000 });

// Check that there are tag links present
const tagLinks = await page.locator('a[href*="/tags/"]').all();
if (tagLinks.length === 0) {
throw new Error('No tag links found');
}

// Verify at least one tag link has the expected format
for (const tagLink of tagLinks) {
const href = await tagLink.getAttribute('href');
if (href && href.includes('/tags/') && href !== '/tags') {
// Found at least one tag with a slug
return;
}
}
throw new Error('No tag links with proper slug format found');
} catch (error) {
throw new Error(`Tags navigation not found or invalid: ${error.message}`);
}
});

Then('the article should have a post header in an H2 element', async function () {
if (!page) {
throw new Error('Page not initialized. Make sure previous steps are executed first.');
}

try {
const h2Header = await page.locator('h2.page-header, h2:has-text("Comparing anker power packs")').first();
await h2Header.waitFor({ state: 'visible', timeout: 10000 });

const headerText = await h2Header.textContent();
if (!headerText || headerText.trim() === '') {
throw new Error('H2 header is empty');
}
} catch (error) {
throw new Error(`Post header H2 element not found: ${error.message}`);
}
});

Then('the page title should contain the post title and "orionrobots"', async function () {
if (!page) {
throw new Error('Page not initialized. Make sure previous steps are executed first.');
}

const title = await page.title();
if (!title.toLowerCase().includes('comparing anker power packs')) {
throw new Error(`Page title does not contain post title. Found: ${title}`);
}
if (!title.toLowerCase().includes('orionrobots')) {
throw new Error(`Page title does not contain "orionrobots". Found: ${title}`);
}
});

Then('the article should have visible images inside the article tag', async function () {
if (!page) {
throw new Error('Page not initialized. Make sure previous steps are executed first.');
}

try {
// Look for images within the article element
const articleImages = await page.locator('article img').all();

if (articleImages.length === 0) {
throw new Error('No images found inside the article tag');
}

// Check that at least one image is visible and has a valid src
let validImagesFound = 0;
for (const img of articleImages) {
try {
await img.waitFor({ state: 'visible', timeout: 5000 });
const src = await img.getAttribute('src');
if (src && src.trim() !== '' && !src.includes('data:')) {
validImagesFound++;
}
} catch (e) {
// Image might not be visible, continue checking others
}
}

if (validImagesFound === 0) {
throw new Error('No visible images with valid src found inside the article tag');
}
} catch (error) {
throw new Error(`Article images check failed: ${error.message}`);
}
});

Then('the article should have a date and author in a div element', async function () {
if (!page) {
throw new Error('Page not initialized. Make sure previous steps are executed first.');
}

try {
// Look for the date and author div
const dateDiv = await page.locator('div.date, div:has(time):has(.author)').first();
await dateDiv.waitFor({ state: 'visible', timeout: 10000 });

// Check for date
const timeElement = await page.locator('time').first();
await timeElement.waitFor({ state: 'visible', timeout: 5000 });

// Check for author
const authorElement = await page.locator('.author, div:has-text("Danny Staple")').first();
await authorElement.waitFor({ state: 'visible', timeout: 5000 });
} catch (error) {
throw new Error(`Date and author div not found: ${error.message}`);
}
});

Then('the page should have a footer with Discord and YouTube links', async function () {
if (!page) {
throw new Error('Page not initialized. Make sure previous steps are executed first.');
}

try {
// Look for Discord link in footer
const discordLink = await page.locator('footer a[href*="discord"]').first();
await discordLink.waitFor({ state: 'visible', timeout: 10000 });

// Look for YouTube link in footer
const youtubeLink = await page.locator('footer a[href*="youtube"]').first();
await youtubeLink.waitFor({ state: 'visible', timeout: 10000 });
} catch (error) {
throw new Error(`Footer with Discord and YouTube links not found: ${error.message}`);
}
});

Then('the page should have the main menu in a nav element at the top', async function () {
if (!page) {
throw new Error('Page not initialized. Make sure previous steps are executed first.');
}

try {
// Look for the main navigation at the top
const mainNav = await page.locator('nav.navbar').first();
await mainNav.waitFor({ state: 'visible', timeout: 10000 });

// Check that it contains menu items
const navItems = await page.locator('nav.navbar .nav-link').all();
if (navItems.length === 0) {
throw new Error('No navigation items found in main nav');
}
} catch (error) {
throw new Error(`Main navigation menu not found: ${error.message}`);
}
});

When('I am in desktop view', async function () {
if (!page) {
throw new Error('Page not initialized. Make sure previous steps are executed first.');
}

// Set viewport to desktop size
await page.setViewportSize({ width: 1200, height: 800 });

// Wait a moment for layout to adjust
await page.waitForTimeout(1000);
});

Then('the images, tables and text should not overflow the article container', async function () {
if (!page) {
throw new Error('Page not initialized. Make sure previous steps are executed first.');
}

try {
// Get the article container bounds
const articleContainer = await page.locator('article, #col-main .content').first();
await articleContainer.waitFor({ state: 'visible', timeout: 10000 });

const containerBox = await articleContainer.boundingBox();
if (!containerBox) {
throw new Error('Could not get article container bounds');
}

// Check images don't overflow
const images = await page.locator('article img, #col-main img').all();
for (const img of images) {
try {
const imgBox = await img.boundingBox();
if (imgBox && imgBox.x + imgBox.width > containerBox.x + containerBox.width + 10) {
throw new Error(`Image overflows container by ${(imgBox.x + imgBox.width) - (containerBox.x + containerBox.width)} pixels`);
}
} catch (e) {
// Image might not be visible, continue
}
}

// Check tables don't overflow
const tables = await page.locator('article table, #col-main table').all();
for (const table of tables) {
try {
const tableBox = await table.boundingBox();
if (tableBox && tableBox.x + tableBox.width > containerBox.x + containerBox.width + 10) {
throw new Error(`Table overflows container by ${(tableBox.x + tableBox.width) - (containerBox.x + containerBox.width)} pixels`);
}
} catch (e) {
// Table might not be visible, continue
}
}

// Check for horizontal scrollbars indicating overflow
const hasHorizontalScrollbar = await page.evaluate(() => {
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
});

if (hasHorizontalScrollbar) {
throw new Error('Page has horizontal scrollbar indicating content overflow');
}
} catch (error) {
throw new Error(`Content overflow check failed: ${error.message}`);
}
});
Loading