260 lines
9.1 KiB
JavaScript
260 lines
9.1 KiB
JavaScript
/**
|
|
* Capture v1.4.x screenshots:
|
|
* - object-detail-with-manual.png #43: documentation_url on object header
|
|
* - task-detail-with-manual.png #43+1.4.1: parent manual link on task page
|
|
* - settings-notification-title-style.png #44: dropdown
|
|
* - overview-v142.png refreshed dashboard with v1.4 polish
|
|
*
|
|
* Same login + panel-traversal pattern as capture-v131-screenshots.mjs.
|
|
*
|
|
* docker compose --profile testing up -d # ensure playwright is up
|
|
* node capture-v14-screenshots.mjs
|
|
*/
|
|
|
|
import { chromium } from "playwright";
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const HA = "http://homeassistant-dev:8123";
|
|
const OUTPUT = path.resolve(__dirname, "../../../docs/images");
|
|
fs.mkdirSync(OUTPUT, { recursive: true });
|
|
|
|
function token() {
|
|
if (process.env.HA_TOKEN) return process.env.HA_TOKEN;
|
|
const env = path.resolve(__dirname, "../../../docker/.env");
|
|
const lines = fs.readFileSync(env, "utf8").split("\n");
|
|
for (const line of lines) {
|
|
if (line.startsWith("HA_TOKEN=")) return line.split("=")[1].trim();
|
|
}
|
|
throw new Error("HA_TOKEN not found");
|
|
}
|
|
|
|
const ACCESS_TOKEN = token();
|
|
|
|
function panelJS() {
|
|
return `
|
|
var ha = document.querySelector('home-assistant');
|
|
var main = ha && ha.shadowRoot && ha.shadowRoot.querySelector('home-assistant-main');
|
|
var drawer = main && main.shadowRoot && main.shadowRoot.querySelector('ha-drawer');
|
|
var resolver = drawer && drawer.querySelector('partial-panel-resolver');
|
|
var custom = resolver && resolver.querySelector('ha-panel-custom');
|
|
var p = custom && custom.querySelector('maintenance-supporter-panel');
|
|
`;
|
|
}
|
|
|
|
async function login(page) {
|
|
await page.goto(HA);
|
|
await page.waitForTimeout(1000);
|
|
await page.evaluate((t) => {
|
|
localStorage.setItem("hassTokens", JSON.stringify({
|
|
hassUrl: "http://homeassistant-dev:8123",
|
|
clientId: "http://homeassistant-dev:8123/",
|
|
refresh_token: "",
|
|
access_token: t,
|
|
token_type: "Bearer",
|
|
expires_in: 86400,
|
|
expires: Date.now() + 86400000,
|
|
}));
|
|
}, ACCESS_TOKEN);
|
|
await page.goto(HA + "/maintenance-supporter");
|
|
await page.waitForTimeout(8000);
|
|
}
|
|
|
|
async function setEnglishDark(page) {
|
|
// The set_user_data round-trip occasionally crashes the page (HA frontend
|
|
// reloads itself when language changes). Wrap in try/catch — if the page
|
|
// crashed, the caller will recreate it via the recovery branch below.
|
|
try {
|
|
await page.evaluate(async () => {
|
|
const ha = document.querySelector("home-assistant");
|
|
const hass = ha && (ha.__hass || ha.hass);
|
|
if (hass && hass.connection) {
|
|
await hass.connection.sendMessagePromise({
|
|
type: "frontend/set_user_data", key: "language",
|
|
value: { language: "en", number_format: "language" },
|
|
});
|
|
await hass.connection.sendMessagePromise({
|
|
type: "frontend/set_user_data", key: "core",
|
|
value: { selectedTheme: { theme: "default", dark: true } },
|
|
});
|
|
}
|
|
});
|
|
} catch (e) {
|
|
console.log(" (language/theme change crashed, recovering)");
|
|
return false;
|
|
}
|
|
await page.goto(HA + "/maintenance-supporter");
|
|
await page.waitForTimeout(8000);
|
|
return true;
|
|
}
|
|
|
|
async function shot(page, name) {
|
|
const filePath = path.join(OUTPUT, name);
|
|
await page.screenshot({ path: filePath });
|
|
console.log(` ${name}`);
|
|
}
|
|
|
|
// ─── Setup helpers ────────────────────────────────────────────────────
|
|
|
|
async function getObjects(page) {
|
|
const raw = await page.evaluate(`{
|
|
${panelJS()}
|
|
var result = [];
|
|
if (p && p._objects) {
|
|
for (var i = 0; i < p._objects.length; i++) {
|
|
var obj = p._objects[i];
|
|
var tasks = [];
|
|
if (obj.tasks) for (var j = 0; j < obj.tasks.length; j++) {
|
|
tasks.push({ id: obj.tasks[j].id, name: obj.tasks[j].name });
|
|
}
|
|
result.push({ entry_id: obj.entry_id, name: obj.object.name, tasks: tasks });
|
|
}
|
|
}
|
|
JSON.stringify(result);
|
|
}`);
|
|
return JSON.parse(raw);
|
|
}
|
|
|
|
async function setObjectDocUrl(page, entryId, url) {
|
|
// v1.4.0 #43: set documentation_url on the object so the new
|
|
// manual-link line renders in the object detail header (and via
|
|
// v1.4.1 also on each task detail page belonging to this object).
|
|
return await page.evaluate(async ({ entry_id, documentation_url }) => {
|
|
const ha = document.querySelector("home-assistant");
|
|
const hass = ha && (ha.__hass || ha.hass);
|
|
return await hass.connection.sendMessagePromise({
|
|
type: "maintenance_supporter/object/update",
|
|
entry_id, documentation_url,
|
|
});
|
|
}, { entry_id: entryId, documentation_url: url });
|
|
}
|
|
|
|
async function setNotificationTitleStyle(page, style) {
|
|
// v1.4.0 #44: flip the global setting so the dropdown screenshot
|
|
// shows it pre-selected (rather than the boring default).
|
|
return await page.evaluate(async ({ s }) => {
|
|
const ha = document.querySelector("home-assistant");
|
|
const hass = ha && (ha.__hass || ha.hass);
|
|
return await hass.connection.sendMessagePromise({
|
|
type: "maintenance_supporter/global/update",
|
|
settings: { notification_title_style: s, notifications_enabled: true },
|
|
});
|
|
}, { s: style });
|
|
}
|
|
|
|
// ─── Main ─────────────────────────────────────────────────────────────
|
|
|
|
const browser = await chromium.connect("ws://localhost:3000");
|
|
console.log("Connected to Playwright server\n");
|
|
|
|
const ctx = await browser.newContext({
|
|
viewport: { width: 1280, height: 900 },
|
|
locale: "en",
|
|
colorScheme: "dark",
|
|
});
|
|
let page = await ctx.newPage();
|
|
|
|
console.log("Login + setup …");
|
|
await login(page);
|
|
const langOk = await setEnglishDark(page);
|
|
if (!langOk) {
|
|
try { await page.close(); } catch { /* ignore */ }
|
|
page = await ctx.newPage();
|
|
await login(page);
|
|
}
|
|
|
|
console.log("\nPick a target object …");
|
|
const objects = await getObjects(page);
|
|
const target = objects.find(o => o.name === "HVAC System")
|
|
|| objects.find(o => o.tasks.length > 0)
|
|
|| objects[0];
|
|
if (!target) {
|
|
console.error("No objects in demo");
|
|
process.exit(1);
|
|
}
|
|
console.log(` using: ${target.name} (${target.tasks.length} tasks)`);
|
|
|
|
console.log("\nSet documentation_url on the object …");
|
|
const r1 = await setObjectDocUrl(page, target.entry_id,
|
|
"https://www.energystar.gov/products/hvac/maintenance-guide.pdf");
|
|
console.log(" result:", JSON.stringify(r1));
|
|
|
|
console.log("\nFlip notification_title_style to object_name + enable notifications …");
|
|
const r2 = await setNotificationTitleStyle(page, "object_name");
|
|
console.log(" flag result keys:", Object.keys(r2 || {}));
|
|
|
|
// Reload so the panel picks up the changes
|
|
await page.goto(HA + "/maintenance-supporter");
|
|
await page.waitForTimeout(6000);
|
|
|
|
// ─── Screenshot 1: object detail with the new documentation URL link ──
|
|
console.log("\n1) object-detail-with-manual.png");
|
|
await page.evaluate(`{
|
|
${panelJS()}
|
|
if (p) p._showObject('${target.entry_id}');
|
|
}`);
|
|
await page.waitForTimeout(2500);
|
|
await shot(page, "object-detail-with-manual.png");
|
|
|
|
// ─── Screenshot 2: task detail with parent object's manual link ──────
|
|
console.log("\n2) task-detail-with-manual.png");
|
|
if (target.tasks.length > 0) {
|
|
const task = target.tasks[0];
|
|
await page.evaluate(`{
|
|
${panelJS()}
|
|
if (p) p._showTask('${target.entry_id}', '${task.id}');
|
|
}`);
|
|
await page.waitForTimeout(3500);
|
|
await shot(page, "task-detail-with-manual.png");
|
|
} else {
|
|
console.log(" skipped — target object has no tasks");
|
|
}
|
|
|
|
// ─── Screenshot 3: notification title style dropdown in Settings ─────
|
|
console.log("\n3) settings-notification-title-style.png");
|
|
await page.evaluate(`{
|
|
${panelJS()}
|
|
if (p) {
|
|
p._view = 'overview';
|
|
p._overviewTab = 'settings';
|
|
p.requestUpdate();
|
|
}
|
|
}`);
|
|
await page.waitForTimeout(3500);
|
|
// Scroll the notification settings section into view
|
|
await page.evaluate(`{
|
|
${panelJS()}
|
|
if (p) {
|
|
var sv = p.shadowRoot && p.shadowRoot.querySelector('maintenance-settings-view');
|
|
if (sv && sv.shadowRoot) {
|
|
// Try to find the title-style row
|
|
var rows = sv.shadowRoot.querySelectorAll('.notif-row, .row, [data-key=notification_title_style]');
|
|
// Fallback: scroll the settings root into the middle of the viewport
|
|
var anchor = sv.shadowRoot.querySelector('select[data-key=notification_title_style]')
|
|
|| sv.shadowRoot.querySelector('[data-key=notification_title_style]')
|
|
|| rows[0];
|
|
if (anchor) anchor.scrollIntoView({behavior: 'instant', block: 'center'});
|
|
}
|
|
}
|
|
}`);
|
|
await page.waitForTimeout(1500);
|
|
await shot(page, "settings-notification-title-style.png");
|
|
|
|
// ─── Screenshot 4: refreshed overview (just dashboard tab) ────────────
|
|
console.log("\n4) overview-v142.png");
|
|
await page.evaluate(`{
|
|
${panelJS()}
|
|
if (p) {
|
|
p._view = 'overview';
|
|
p._overviewTab = 'dashboard';
|
|
p.requestUpdate();
|
|
}
|
|
}`);
|
|
await page.waitForTimeout(2500);
|
|
await shot(page, "overview-v142.png");
|
|
|
|
console.log("\nDone.");
|
|
await browser.close();
|