@casoon/auditmysite
Version:
A comprehensive command-line tool for automated accessibility, security, performance, and SEO testing using Playwright and pa11y, based on sitemap URLs
253 lines • 11.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PWATest = void 0;
const base_test_1 = require("../base-test");
class PWATest extends base_test_1.BaseAccessibilityTest {
constructor() {
super(...arguments);
this.name = 'PWA Test';
this.description = 'Tests Progressive Web App features including manifest, service worker, and installability';
this.category = 'mobile';
this.priority = 'medium';
this.standards = ['PWA', 'Web App Manifest', 'Service Worker'];
}
async run(context) {
const startTime = Date.now();
try {
// Navigate to page
await context.page.goto(context.url, { waitUntil: 'networkidle' });
// Wait for page to be fully loaded
await context.page.waitForTimeout(2000);
// Test PWA features
const manifest = await this.checkManifest(context.page);
const hasServiceWorker = await this.checkServiceWorker(context.page);
const features = await this.checkPWAFeatures(context.page);
// Calculate score
const score = this.calculateScore(features, manifest, hasServiceWorker);
const isInstallable = this.checkInstallability(features, manifest);
const recommendations = this.generateRecommendations(features, manifest, hasServiceWorker);
const duration = Date.now() - startTime;
return {
passed: score >= 70,
count: features.length,
errors: features.filter(f => !f.implemented && f.score === 0).map(f => `${f.name}: ${f.description}`),
warnings: features.filter(f => !f.implemented && f.score > 0).map(f => `${f.name}: ${f.description}`),
details: {
manifest,
hasServiceWorker,
hasManifest: !!manifest,
isInstallable,
score,
features,
recommendations,
duration,
url: context.url,
testName: this.name,
description: this.description
}
};
}
catch (error) {
return {
passed: false,
count: 0,
errors: [`Error occurred during PWA analysis: ${error}`],
warnings: [],
details: {
manifest: null,
hasServiceWorker: false,
hasManifest: false,
isInstallable: false,
score: 0,
features: [],
duration: Date.now() - startTime,
url: context.url,
testName: this.name,
description: this.description
}
};
}
}
async checkManifest(page) {
try {
const manifest = await page.evaluate(() => {
const manifestLink = document.querySelector('link[rel="manifest"]');
if (!manifestLink)
return null;
// Try to fetch manifest
return fetch(manifestLink.href)
.then(response => response.json())
.catch(() => null);
});
return manifest;
}
catch {
return null;
}
}
async checkServiceWorker(page) {
try {
return await page.evaluate(() => {
return 'serviceWorker' in navigator && navigator.serviceWorker.controller !== null;
});
}
catch {
return false;
}
}
async checkPWAFeatures(page) {
const features = [];
// Check manifest
const manifest = await this.checkManifest(page);
features.push({
name: 'Web App Manifest',
description: 'Web app manifest file is present and valid',
implemented: !!manifest,
score: manifest ? 100 : 0,
details: manifest ? 'Manifest found and loaded successfully' : 'No manifest found'
});
// Check service worker
const hasServiceWorker = await this.checkServiceWorker(page);
features.push({
name: 'Service Worker',
description: 'Service worker is registered and active',
implemented: hasServiceWorker,
score: hasServiceWorker ? 100 : 0,
details: hasServiceWorker ? 'Service worker is active' : 'No service worker found'
});
// Check HTTPS
const isHTTPS = await page.evaluate(() => location.protocol === 'https:');
features.push({
name: 'HTTPS',
description: 'Site is served over HTTPS',
implemented: isHTTPS,
score: isHTTPS ? 100 : 0,
details: isHTTPS ? 'Site is served over HTTPS' : 'Site is not served over HTTPS'
});
// Check responsive design
const isResponsive = await page.evaluate(() => {
const viewport = document.querySelector('meta[name="viewport"]');
return !!(viewport && viewport.getAttribute('content')?.includes('width=device-width'));
});
features.push({
name: 'Responsive Design',
description: 'Viewport meta tag is properly configured',
implemented: isResponsive,
score: isResponsive ? 100 : 0,
details: isResponsive ? 'Viewport meta tag found' : 'No viewport meta tag found'
});
// Check offline capability
const hasOfflineCapability = await page.evaluate(() => {
return 'caches' in window || 'indexedDB' in window;
});
features.push({
name: 'Offline Capability',
description: 'Site has offline functionality',
implemented: hasOfflineCapability,
score: hasOfflineCapability ? 100 : 50,
details: hasOfflineCapability ? 'Offline capabilities detected' : 'No offline capabilities detected'
});
// Check app-like experience
const hasAppLikeExperience = await page.evaluate(() => {
const displayMode = window.matchMedia('(display-mode: standalone)').matches;
return displayMode;
});
features.push({
name: 'App-like Experience',
description: 'Site provides app-like experience',
implemented: hasAppLikeExperience,
score: hasAppLikeExperience ? 100 : 50,
details: hasAppLikeExperience ? 'App-like experience detected' : 'Standard web experience'
});
// Check manifest properties
if (manifest) {
// Check name
features.push({
name: 'App Name',
description: 'App has a name defined in manifest',
implemented: !!(manifest.name || manifest.short_name),
score: (manifest.name || manifest.short_name) ? 100 : 0,
details: manifest.name || manifest.short_name ? `App name: ${manifest.name || manifest.short_name}` : 'No app name defined'
});
// Check icons
features.push({
name: 'App Icons',
description: 'App has icons defined in manifest',
implemented: !!(manifest.icons && manifest.icons.length > 0),
score: (manifest.icons && manifest.icons.length > 0) ? 100 : 0,
details: manifest.icons && manifest.icons.length > 0 ? `${manifest.icons.length} icons defined` : 'No icons defined'
});
// Check display mode
features.push({
name: 'Display Mode',
description: 'App has display mode defined',
implemented: !!manifest.display,
score: manifest.display ? 100 : 50,
details: manifest.display ? `Display mode: ${manifest.display}` : 'No display mode defined'
});
// Check theme color
features.push({
name: 'Theme Color',
description: 'App has theme color defined',
implemented: !!manifest.theme_color,
score: manifest.theme_color ? 100 : 50,
details: manifest.theme_color ? `Theme color: ${manifest.theme_color}` : 'No theme color defined'
});
}
return features;
}
calculateScore(features, manifest, hasServiceWorker) {
if (features.length === 0)
return 0;
const totalScore = features.reduce((sum, feature) => sum + feature.score, 0);
return Math.round(totalScore / features.length);
}
checkInstallability(features, manifest) {
const requiredFeatures = features.filter(f => f.name === 'Web App Manifest' ||
f.name === 'HTTPS' ||
f.name === 'App Name' ||
f.name === 'App Icons');
return requiredFeatures.every(f => f.implemented);
}
generateRecommendations(features, manifest, hasServiceWorker) {
const recommendations = [];
if (!manifest) {
recommendations.push('Create a web app manifest file (manifest.json)');
recommendations.push('Add manifest link to HTML: <link rel="manifest" href="/manifest.json">');
}
if (!hasServiceWorker) {
recommendations.push('Register a service worker for offline functionality');
recommendations.push('Implement caching strategies for better performance');
}
if (!manifest?.name && !manifest?.short_name) {
recommendations.push('Add app name to manifest (name and short_name properties)');
}
if (!manifest?.icons || manifest.icons.length === 0) {
recommendations.push('Add app icons to manifest (multiple sizes recommended)');
recommendations.push('Include at least 192x192 and 512x512 pixel icons');
}
if (!manifest?.display) {
recommendations.push('Add display mode to manifest (standalone, fullscreen, or minimal-ui)');
}
if (!manifest?.theme_color) {
recommendations.push('Add theme color to manifest for consistent branding');
}
if (!manifest?.background_color) {
recommendations.push('Add background color to manifest for splash screen');
}
const httpsFeature = features.find(f => f.name === 'HTTPS');
if (httpsFeature && !httpsFeature.implemented) {
recommendations.push('Serve the site over HTTPS (required for PWA features)');
}
const responsiveFeature = features.find(f => f.name === 'Responsive Design');
if (responsiveFeature && !responsiveFeature.implemented) {
recommendations.push('Add viewport meta tag: <meta name="viewport" content="width=device-width, initial-scale=1">');
}
if (recommendations.length === 0) {
recommendations.push('All PWA features are properly implemented');
}
return recommendations;
}
}
exports.PWATest = PWATest;
//# sourceMappingURL=pwa-test.js.map