chore(qa): restore reusable workflow with Gitea compatibility fixes
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 8s
Nightly QA / call-qa-workflow (push) Failing after 1m34s
🚀 Build & Deploy / 🧪 QA (push) Has been cancelled
🚀 Build & Deploy / 🚀 Deploy (push) Has been cancelled
🚀 Build & Deploy / 🏗️ Build (push) Has been cancelled
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
🚀 Build & Deploy / 🔔 Notify (push) Has been cancelled
Some checks failed
🚀 Build & Deploy / 🔍 Prepare (push) Successful in 8s
Nightly QA / call-qa-workflow (push) Failing after 1m34s
🚀 Build & Deploy / 🧪 QA (push) Has been cancelled
🚀 Build & Deploy / 🚀 Deploy (push) Has been cancelled
🚀 Build & Deploy / 🏗️ Build (push) Has been cancelled
🚀 Build & Deploy / 🧪 Post-Deploy Verification (push) Has been cancelled
🚀 Build & Deploy / 🔔 Notify (push) Has been cancelled
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import axios from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { execSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { AxiosError } from 'axios';
|
||||
import axios from "axios";
|
||||
import * as cheerio from "cheerio";
|
||||
import { execSync } from "child_process";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { AxiosError } from "axios";
|
||||
|
||||
interface Pa11yConfig {
|
||||
defaults: {
|
||||
@@ -36,9 +36,14 @@ interface Pa11yResult {
|
||||
* 3. Runs pa11y-ci on those URLs
|
||||
*/
|
||||
|
||||
const targetUrl = process.argv[2] || process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
|
||||
const limit = process.env.PAGESPEED_LIMIT ? parseInt(process.env.PAGESPEED_LIMIT) : 20;
|
||||
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || 'mbgrid';
|
||||
const targetUrl =
|
||||
process.argv[2] ||
|
||||
process.env.NEXT_PUBLIC_BASE_URL ||
|
||||
"http://localhost:3000";
|
||||
const limit = process.env.PAGESPEED_LIMIT
|
||||
? parseInt(process.env.PAGESPEED_LIMIT)
|
||||
: 20;
|
||||
const gatekeeperPassword = process.env.GATEKEEPER_PASSWORD || "mbgrid";
|
||||
|
||||
async function main() {
|
||||
console.log(`\n🚀 Starting WCAG Audit for: ${targetUrl}`);
|
||||
@@ -46,32 +51,32 @@ async function main() {
|
||||
|
||||
try {
|
||||
// 1. Fetch Sitemap
|
||||
const sitemapUrl = `${targetUrl.replace(/\/$/, '')}/sitemap.xml`;
|
||||
const sitemapUrl = `${targetUrl.replace(/\/$/, "")}/sitemap.xml`;
|
||||
console.log(`📥 Fetching sitemap from ${sitemapUrl}...`);
|
||||
|
||||
const response = await axios.get(sitemapUrl, {
|
||||
headers: {
|
||||
Cookie: `mb_gatekeeper_session=${gatekeeperPassword}`,
|
||||
Cookie: `mintel_gatekeeper_session=${gatekeeperPassword}`,
|
||||
},
|
||||
validateStatus: (status) => status < 400,
|
||||
});
|
||||
|
||||
const $ = cheerio.load(response.data, { xmlMode: true });
|
||||
let urls = $('url loc')
|
||||
let urls = $("url loc")
|
||||
.map((i, el) => $(el).text())
|
||||
.get();
|
||||
|
||||
// Cleanup, filter and normalize domains to targetUrl
|
||||
const urlPattern = /https?:\/\/[^\/]+/;
|
||||
urls = [...new Set(urls)]
|
||||
.filter((u) => u.startsWith('http'))
|
||||
.map((u) => u.replace(urlPattern, targetUrl.replace(/\/$/, '')))
|
||||
.filter((u) => u.startsWith("http"))
|
||||
.map((u) => u.replace(urlPattern, targetUrl.replace(/\/$/, "")))
|
||||
.sort();
|
||||
|
||||
console.log(`✅ Found ${urls.length} URLs in sitemap.`);
|
||||
|
||||
if (urls.length === 0) {
|
||||
console.error('❌ No URLs found in sitemap. Is the site up?');
|
||||
console.error("❌ No URLs found in sitemap. Is the site up?");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -79,7 +84,9 @@ async function main() {
|
||||
console.log(
|
||||
`⚠️ Too many pages (${urls.length}). Limiting to ${limit} representative pages.`,
|
||||
);
|
||||
const home = urls.filter((u) => u.endsWith('/de') || u.endsWith('/en') || u === targetUrl);
|
||||
const home = urls.filter(
|
||||
(u) => u.endsWith("/de") || u.endsWith("/en") || u === targetUrl,
|
||||
);
|
||||
const others = urls.filter((u) => !home.includes(u));
|
||||
urls = [...home, ...others.slice(0, limit - home.length)];
|
||||
}
|
||||
@@ -88,10 +95,10 @@ async function main() {
|
||||
urls.forEach((u) => console.log(` - ${u}`));
|
||||
|
||||
// 2. Prepare pa11y-ci config
|
||||
const baseConfigPath = path.join(process.cwd(), '.pa11yci.json');
|
||||
const baseConfigPath = path.join(process.cwd(), ".pa11yci.json");
|
||||
let baseConfig: Pa11yConfig = { defaults: {} };
|
||||
if (fs.existsSync(baseConfigPath)) {
|
||||
baseConfig = JSON.parse(fs.readFileSync(baseConfigPath, 'utf8'));
|
||||
baseConfig = JSON.parse(fs.readFileSync(baseConfigPath, "utf8"));
|
||||
}
|
||||
|
||||
// Extract domain for cookie (not currently used)
|
||||
@@ -105,19 +112,21 @@ async function main() {
|
||||
defaults: {
|
||||
...baseConfig.defaults,
|
||||
threshold: 0, // Force threshold to 0 so all errors are shown in JSON
|
||||
runners: ['axe'],
|
||||
ignore: [...(baseConfig.defaults?.ignore || []), 'color-contrast'],
|
||||
runners: ["axe"],
|
||||
ignore: [...(baseConfig.defaults?.ignore || []), "color-contrast"],
|
||||
chromeLaunchConfig: {
|
||||
...baseConfig.defaults?.chromeLaunchConfig,
|
||||
...(process.env.CHROME_PATH ? { executablePath: process.env.CHROME_PATH } : {}),
|
||||
...(process.env.CHROME_PATH
|
||||
? { executablePath: process.env.CHROME_PATH }
|
||||
: {}),
|
||||
args: [
|
||||
...(baseConfig.defaults?.chromeLaunchConfig?.args || []),
|
||||
'--no-sandbox',
|
||||
'--disable-setuid-sandbox',
|
||||
"--no-sandbox",
|
||||
"--disable-setuid-sandbox",
|
||||
],
|
||||
},
|
||||
headers: {
|
||||
Cookie: `mb_gatekeeper_session=${gatekeeperPassword}`,
|
||||
Cookie: `mintel_gatekeeper_session=${gatekeeperPassword}`,
|
||||
},
|
||||
timeout: 60000, // Increase timeout for slower pages
|
||||
},
|
||||
@@ -125,13 +134,13 @@ async function main() {
|
||||
};
|
||||
|
||||
// Create output directory
|
||||
const outputDir = path.join(process.cwd(), '.pa11yci');
|
||||
const outputDir = path.join(process.cwd(), ".pa11yci");
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const tempConfigPath = path.join(outputDir, 'config.temp.json');
|
||||
const reportPath = path.join(outputDir, 'report.json');
|
||||
const tempConfigPath = path.join(outputDir, "config.temp.json");
|
||||
const reportPath = path.join(outputDir, "report.json");
|
||||
fs.writeFileSync(tempConfigPath, JSON.stringify(tempConfig, null, 2));
|
||||
|
||||
// 3. Execute pa11y-ci
|
||||
@@ -140,8 +149,8 @@ async function main() {
|
||||
|
||||
try {
|
||||
execSync(pa11yCommand, {
|
||||
encoding: 'utf8',
|
||||
stdio: 'inherit',
|
||||
encoding: "utf8",
|
||||
stdio: "inherit",
|
||||
});
|
||||
} catch {
|
||||
// pa11y-ci exits with non-zero if issues are found, which is expected
|
||||
@@ -149,7 +158,7 @@ async function main() {
|
||||
|
||||
// 4. Summarize Results
|
||||
if (fs.existsSync(reportPath)) {
|
||||
const reportData = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
||||
const reportData = JSON.parse(fs.readFileSync(reportPath, "utf8"));
|
||||
console.log(`\n📊 WCAG Audit Summary:\n`);
|
||||
|
||||
const summaryTable = Object.keys(reportData.results).map((url) => {
|
||||
@@ -161,31 +170,47 @@ async function main() {
|
||||
|
||||
if (Array.isArray(results)) {
|
||||
// pa11y action execution errors come as objects with a message but no type
|
||||
const actionErrors = results.filter((r: Pa11yResult) => !r.type && r.message).length;
|
||||
errors = results.filter((r: Pa11yResult) => r.type === 'error').length + actionErrors;
|
||||
warnings = results.filter((r: Pa11yResult) => r.type === 'warning').length;
|
||||
notices = results.filter((r: Pa11yResult) => r.type === 'notice').length;
|
||||
const actionErrors = results.filter(
|
||||
(r: Pa11yResult) => !r.type && r.message,
|
||||
).length;
|
||||
errors =
|
||||
results.filter((r: Pa11yResult) => r.type === "error").length +
|
||||
actionErrors;
|
||||
warnings = results.filter(
|
||||
(r: Pa11yResult) => r.type === "warning",
|
||||
).length;
|
||||
notices = results.filter(
|
||||
(r: Pa11yResult) => r.type === "notice",
|
||||
).length;
|
||||
}
|
||||
|
||||
// Clean URL for display
|
||||
const displayUrl = url.replace(targetUrl, '') || '/';
|
||||
const displayUrl = url.replace(targetUrl, "") || "/";
|
||||
|
||||
return {
|
||||
URL: displayUrl.length > 50 ? displayUrl.substring(0, 47) + '...' : displayUrl,
|
||||
URL:
|
||||
displayUrl.length > 50
|
||||
? displayUrl.substring(0, 47) + "..."
|
||||
: displayUrl,
|
||||
Errors: errors,
|
||||
Warnings: warnings,
|
||||
Notices: notices,
|
||||
Status: errors === 0 ? '✅' : '❌',
|
||||
Status: errors === 0 ? "✅" : "❌",
|
||||
};
|
||||
});
|
||||
|
||||
console.table(summaryTable);
|
||||
|
||||
const totalErrors = summaryTable.reduce((acc, curr) => acc + curr.Errors, 0);
|
||||
const totalErrors = summaryTable.reduce(
|
||||
(acc, curr) => acc + curr.Errors,
|
||||
0,
|
||||
);
|
||||
const totalPages = summaryTable.length;
|
||||
const cleanPages = summaryTable.filter((p) => p.Errors === 0).length;
|
||||
|
||||
console.log(`\n📈 Result: ${cleanPages}/${totalPages} pages are error-free.`);
|
||||
console.log(
|
||||
`\n📈 Result: ${cleanPages}/${totalPages} pages are error-free.`,
|
||||
);
|
||||
if (totalErrors > 0) {
|
||||
console.log(` Total Errors discovered: ${totalErrors}`);
|
||||
process.exitCode = 1;
|
||||
@@ -205,7 +230,10 @@ async function main() {
|
||||
process.exit(1);
|
||||
} finally {
|
||||
// Clean up temp config file, keep report
|
||||
const tempConfigPath = path.join(process.cwd(), '.pa11yci/config.temp.json');
|
||||
const tempConfigPath = path.join(
|
||||
process.cwd(),
|
||||
".pa11yci/config.temp.json",
|
||||
);
|
||||
if (fs.existsSync(tempConfigPath)) fs.unlinkSync(tempConfigPath);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user