Files
pulse/scripts/take-screenshots.js
T

246 lines
12 KiB
JavaScript

const { chromium } = require('playwright');
const path = require('path');
const fs = require('fs');
// --- Configuration ---
const BASE_URL = process.env.PULSE_URL || 'http://localhost:7655'; // Allow overriding via env var
const OUTPUT_DIR = path.resolve(__dirname, '../docs/images');
const VIEWPORT = { width: 1440, height: 900 }; // Match common M1 Air scaled resolution (16:10)
const WAIT_OPTIONS = { waitUntil: 'networkidle', timeout: 15000 }; // Increased timeout, networkidle
const OVERLAY_SELECTOR = '#loading-overlay';
// Define the sections to capture
// Placeholder selectors/navigation logic will need refinement
const sections = [
// Dashboard: Wait for guest row, capture FULL page
{ name: '01-dashboard',
fullPage: true,
action: async (page) => {
console.log(' Action: Waiting for dashboard content to load (checking for removal of loading text)...');
// Wait for the first row that DOES NOT contain the loading text TD
await page.locator('#main-table tbody tr:not(:has(td:text("Loading data...")))').first().waitFor({ state: 'visible', timeout: 30000 });
console.log(' Action: Dashboard content loaded (loading text gone).');
}
},
// VM View: Click main tab, click VM filter, wait, capture main content
{ name: '02-vm-container-view', // Renumbered from 03
screenshotTarget: '#nested-tab-dashboard', // Changed from #main
action: async (page) => {
console.log(' Action: Clicking Main tab (if not already active)');
// Ensure main tab is active first
const mainTabIsActive = await page.locator('[data-tab="main"].active').isVisible();
if (!mainTabIsActive) {
await page.locator('[data-tab="main"]').click();
await page.waitForLoadState('networkidle', { timeout: 5000 });
}
// Wait for the VM filter button's LABEL to be visible before clicking
console.log(' Action: Waiting for VM filter label to be visible');
const vmFilterLabel = page.locator('label[for="filter-vm"]'); // Target the label now
await vmFilterLabel.waitFor({ state: 'visible', timeout: 15000 });
console.log(' Action: VM filter label visible');
console.log(' Action: Clicking VM filter label');
await vmFilterLabel.click(); // Click the label
await page.waitForTimeout(1000);
console.log(' Action: VM filter applied');
// Hide node summary cards
console.log(' Action: Hiding node summary cards');
await page.locator('#node-summary-cards-container').evaluate(element => element.style.display = 'none');
},
// Add a postAction to make the cards visible again
postAction: async (page) => {
console.log(' Action: Showing node summary cards');
await page.locator('#node-summary-cards-container').evaluate(element => element.style.display = ''); // Reset display
}
},
// PBS View: Click tab, wait for PBS container content, capture PBS tab content
{ name: '03-pbs-view', // Renumbered from 04
screenshotTarget: '#pbs',
action: async (page) => {
console.log(' Action: Clicking PBS tab');
await page.locator('[data-tab="pbs"]').click();
console.log(' Action: Waiting for PBS container to be visible');
// Wait for the main container within the PBS tab to be visible
await page.locator('#pbs #pbs-instances-container').waitFor({ state: 'visible', timeout: 10000 });
console.log(' Action: PBS container visible');
// Optional: wait for actual content rows if needed
// await page.locator('#pbs-instances-container > *').first().waitFor({ state: 'visible', timeout: 10000 });
}
},
// Backups View: Click tab, wait for table content, capture backups tab content
{ name: '04-backups-view', // Renumbered from 05
screenshotTarget: '#backups',
action: async (page) => {
console.log(' Action: Clicking Backups tab');
await page.locator('[data-tab="backups"]').click();
console.log(' Action: Waiting for backups table row to be visible');
// Wait for the first row in the backups table body
await page.locator('#backups-overview-tbody tr').first().waitFor({ state: 'visible', timeout: 15000 });
console.log(' Action: Backups table row visible');
}
},
// Storage View: Click tab, wait for table content, capture storage tab content
{ name: '05-storage-view', // Renumbered
screenshotTarget: '#storage-info-content',
action: async (page) => {
await page.locator('[data-tab="storage"]').click();
await page.locator('#storage-info-content table').waitFor({ state: 'visible', timeout: 10000 }); // Wait for table to be populated
await page.waitForTimeout(500); // Allow animations/renders
}
}
// { name: '06-task-view', url: '/#tasks', screenshotTarget: '#task-list-element', action: async (page) => { /* Navigate to task view if separate */ } }, // Uncomment and adjust if needed
];
async function takeScreenshots() {
console.log(`Starting screenshot capture for ${BASE_URL}...`);
console.log(`Outputting to: ${OUTPUT_DIR}`);
// --- Clean up existing PNG files ---
if (fs.existsSync(OUTPUT_DIR)) {
console.log(`Cleaning up existing *.png files in ${OUTPUT_DIR}...`);
const files = fs.readdirSync(OUTPUT_DIR);
let deletedCount = 0;
files.forEach(file => {
if (path.extname(file).toLowerCase() === '.png') {
const filePath = path.join(OUTPUT_DIR, file);
try {
fs.unlinkSync(filePath);
// console.log(` Deleted: ${file}`); // Optional: more verbose logging
deletedCount++;
} catch (err) {
console.error(` Error deleting file ${file}: ${err.message}`);
}
}
});
console.log(`Cleanup finished. Deleted ${deletedCount} PNG file(s).`);
} else {
console.log('Output directory does not exist, no cleanup needed.');
}
// --- End Cleanup ---
// Ensure output directory exists (might have been deleted if empty or just created)
if (!fs.existsSync(OUTPUT_DIR)) {
console.log(`Creating directory: ${OUTPUT_DIR}`);
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
let browser;
try {
browser = await chromium.launch(); // Or firefox, webkit
const context = await browser.newContext({
viewport: VIEWPORT,
ignoreHTTPSErrors: true, // Helpful if using self-signed certs for Pulse dev
deviceScaleFactor: 2 // Render at 2x detail for higher quality screenshots
});
const page = await context.newPage();
console.log('Navigating to base URL and waiting for initial load...');
await page.goto(BASE_URL, WAIT_OPTIONS);
// Wait for the loading overlay to disappear before starting captures
console.log(`Waiting for overlay (${OVERLAY_SELECTOR}) to disappear...`);
await page.locator(OVERLAY_SELECTOR).waitFor({ state: 'hidden', timeout: 20000 }); // Increased timeout
console.log('Overlay hidden.');
// --- Ensure Dark Mode ---
console.log('Ensuring dark mode is active...');
const isDarkMode = await page.evaluate(() => document.documentElement.classList.contains('dark'));
if (!isDarkMode) {
console.log(' Dark mode not active, clicking theme toggle button...');
const themeButton = page.locator('#theme-toggle-button');
await themeButton.waitFor({ state: 'visible', timeout: 5000 });
await themeButton.click();
await page.waitForTimeout(500); // Wait for theme transition
console.log(' Dark mode toggled.');
} else {
console.log(' Dark mode already active.');
}
// --- End Ensure Dark Mode ---
console.log('Starting section captures.');
for (const section of sections) {
const screenshotPath = path.join(OUTPUT_DIR, `${section.name}.png`);
console.log(`Capturing section: ${section.name}...`);
try {
// Perform specific actions if needed (clicks, etc.)
if (section.action) {
console.log(` Performing action for ${section.name}...`);
await section.action(page);
// Wait after action for UI to settle - using networkidle should be sufficient
await page.waitForLoadState('networkidle', { timeout: 10000 });
console.log(' Action completed and network idle.');
} else {
// Ensure we are on the main tab for sections without specific actions
// (Applies mainly if dashboard wasn't the very first step)
const mainTabIsActive = await page.locator('[data-tab="main"].active').isVisible();
if (!mainTabIsActive) {
await page.locator('[data-tab="main"]').click();
await page.waitForLoadState('networkidle', { timeout: 5000 });
}
}
// Take the screenshot
let elementToCapture;
let captureFullPage = section.fullPage || false; // Get flag, default false
console.log(` Locating screenshot target: ${section.screenshotTarget || 'page (fullPage: '+captureFullPage+')'}`);
if (section.screenshotTarget) { // Check if a specific target is defined
elementToCapture = page.locator(section.screenshotTarget).first();
console.log(' Waiting for screenshot target element to be visible...');
await elementToCapture.waitFor({ state: 'visible', timeout: 10000 });
console.log(' Target element visible.');
captureFullPage = false; // Never capture full page when targeting a specific element
} else {
elementToCapture = page; // Use the page itself for viewport/fullpage screenshots
}
console.log(` Saving screenshot to: ${screenshotPath}`);
if (elementToCapture === page) {
// Capture viewport or full page based on the flag
console.log(` Capturing ${captureFullPage ? 'full page' : 'viewport'}`);
await page.screenshot({ path: screenshotPath, fullPage: captureFullPage });
} else {
// Capture specific element
console.log(' Capturing specific element');
await elementToCapture.screenshot({ path: screenshotPath });
}
console.log(` Successfully captured ${section.name}`);
// Perform post-action if defined (e.g., to restore UI state)
if (section.postAction) {
console.log(` Performing post-action for ${section.name}...`);
await section.postAction(page);
// Optionally wait for UI to settle after post-action
await page.waitForLoadState('networkidle', { timeout: 5000 });
console.log(' Post-action completed and network idle.');
}
} catch (error) {
console.error(` Failed to capture section ${section.name}: ${error.message}`);
// Optionally, decide if you want to continue or stop on error
}
}
} catch (error) {
console.error(`Error during screenshot process: ${error}`);
process.exitCode = 1; // Indicate failure
} finally {
if (browser) {
await browser.close();
console.log('Browser closed.');
}
}
console.log('Screenshot capture finished.');
}
takeScreenshots();