Skip to content

Commit fc6ecc5

Browse files
committed
feature. Added tests for page metadata, sitemap, and feed.
1 parent a7542ab commit fc6ecc5

File tree

7 files changed

+322
-2
lines changed

7 files changed

+322
-2
lines changed

.github/workflows/ci-cd.yml

+14
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,20 @@ jobs:
4747
npm install
4848
npm run build
4949
50+
- name: Run Playwright Tests
51+
id: test
52+
run: |
53+
npx playwright install
54+
npx playwright test --reporter=html
55+
env:
56+
CI: true
57+
58+
- name: Upload Playwright Test Report
59+
uses: actions/upload-artifact@v3
60+
with:
61+
name: playwright-test-report
62+
path: playwright-report
63+
5064
- name: Upload Site Files as Artifact
5165
uses: actions/upload-pages-artifact@v3
5266
with:

.gitignore

+6
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,9 @@ Cargo.lock
106106

107107
# MSVC Windows builds of rustc generate these, which store debugging information
108108
*.pdb
109+
110+
# Playwright
111+
/test-results/
112+
/playwright-report/
113+
/blob-report/
114+
/playwright/.cache/

package-lock.json

+114
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"url": "git+https://github.com/davidwesst/website.git"
1212
},
1313
"engines": {
14-
"node": "^22"
14+
"node": "^23"
1515
},
1616
"scripts": {
1717
"prebuild": "concurrently 'npm run clean' 'npm run generate-tags'",
@@ -24,7 +24,8 @@
2424
"debug:eleventy": "DEBUG=Eleventy* eleventy",
2525
"generate-tags": "./tools/tag-helper.js 'content/blog' 'data/content-tags.json'",
2626
"prestart": "npm run build",
27-
"start": "http-server ./dist --port 8081"
27+
"start": "http-server ./dist --port 8081",
28+
"test": "playwright test --config=playwright.config.js"
2829
},
2930
"devDependencies": {
3031
"@11ty/eleventy": "^3.0.0",
@@ -33,12 +34,15 @@
3334
"@11ty/eleventy-plugin-rss": "^2.0.2",
3435
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0",
3536
"@11ty/eleventy-plugin-webc": "^0.11.2",
37+
"@playwright/test": "^1.51.1",
38+
"@types/node": "^22.14.1",
3639
"concurrently": "^9.1.0",
3740
"date-fns": "^4.1.0",
3841
"gray-matter": "^4.0.3",
3942
"http-server": "^14.1.1",
4043
"rimraf": "^6.0.1",
4144
"simple-git": "^3.27.0",
45+
"xml2js": "^0.6.2",
4246
"yaml": "^2.6.1"
4347
},
4448
"type": "module"

playwright.config.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// @ts-check
2+
import { defineConfig, devices } from '@playwright/test';
3+
4+
/**
5+
* Read environment variables from file.
6+
* https://github.com/motdotla/dotenv
7+
*/
8+
// import dotenv from 'dotenv';
9+
// import path from 'path';
10+
// dotenv.config({ path: path.resolve(__dirname, '.env') });
11+
12+
/**
13+
* @see https://playwright.dev/docs/test-configuration
14+
*/
15+
export default defineConfig({
16+
testDir: './tests',
17+
/* Run tests in files in parallel */
18+
fullyParallel: true,
19+
/* Fail the build on CI if you accidentally left test.only in the source code. */
20+
forbidOnly: !!process.env.CI,
21+
/* Retry on CI only */
22+
retries: process.env.CI ? 2 : 0,
23+
/* Opt out of parallel tests on CI. */
24+
workers: process.env.CI ? 1 : undefined,
25+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
26+
reporter: 'html',
27+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
28+
use: {
29+
/* Base URL to use in actions like `await page.goto('/')`. */
30+
baseURL: 'http://localhost:8080',
31+
32+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
33+
trace: 'on-first-retry',
34+
},
35+
36+
/* Configure projects for major browsers */
37+
projects: [
38+
{
39+
name: 'chromium',
40+
use: { ...devices['Desktop Chrome'] },
41+
},
42+
43+
{
44+
name: 'firefox',
45+
use: { ...devices['Desktop Firefox'] },
46+
},
47+
48+
{
49+
name: 'webkit',
50+
use: { ...devices['Desktop Safari'] },
51+
},
52+
53+
/* Test against mobile viewports. */
54+
// {
55+
// name: 'Mobile Chrome',
56+
// use: { ...devices['Pixel 5'] },
57+
// },
58+
// {
59+
// name: 'Mobile Safari',
60+
// use: { ...devices['iPhone 12'] },
61+
// },
62+
63+
/* Test against branded browsers. */
64+
// {
65+
// name: 'Microsoft Edge',
66+
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
67+
// },
68+
// {
69+
// name: 'Google Chrome',
70+
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
71+
// },
72+
],
73+
74+
webServer: {
75+
command: 'npm run dev',
76+
url: 'http://127.0.0.1:8080',
77+
reuseExistingServer: !process.env.CI,
78+
},
79+
});
80+

tests/metadata.spec.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// @ts-check
2+
import { test, expect } from '@playwright/test';
3+
import fetch from 'node-fetch';
4+
import { parseStringPromise } from 'xml2js';
5+
6+
async function getRoutesFromSitemap(sitemapUrl) {
7+
const response = await fetch(sitemapUrl);
8+
9+
// Check if the response is OK
10+
if (!response.ok) {
11+
throw new Error(`Failed to fetch sitemap from url ${sitemapUrl}. Status: ${response.status} ${response.statusText}`);
12+
}
13+
14+
const xml = await response.text();
15+
16+
const parsed = await parseStringPromise(xml);
17+
18+
const routes = parsed.urlset.url
19+
.map(u => new URL(u.loc[0]).pathname)
20+
.filter(path => !path.endsWith('.xml'));
21+
22+
return routes;
23+
}
24+
25+
test.describe('Page Metadata Checks', () => {
26+
test.setTimeout(60000); // Set timeout to 60 seconds
27+
28+
let routes;
29+
30+
test.beforeAll(async () => {
31+
// Replace with your sitemap URL
32+
const sitemapUrl = 'http://localhost:8080/sitemap.xml';
33+
routes = await getRoutesFromSitemap(sitemapUrl);
34+
});
35+
36+
test('all pages have meta description', async ({ page }) => {
37+
for (const route of routes) {
38+
await page.goto(`${route}`);
39+
const metaDescription = await page.locator('meta[name="description"]');
40+
await expect(metaDescription).toHaveAttribute('content', /.+/); // Ensure 'content' is not empty
41+
}
42+
});
43+
44+
test('all pages have a title', async ({ page }) => {
45+
for (const route of routes) {
46+
await page.goto(`${route}`);
47+
const pageTitle = await page.title();
48+
expect(pageTitle).not.toBe(''); // Ensure the title is not empty
49+
}
50+
});
51+
});

0 commit comments

Comments
 (0)