New apps Added
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Capture v1.3.1 screenshots: completion-actions feature flag toggle, the
|
||||
* task-dialog On-Complete Action section with the new ha-service-picker,
|
||||
* and the schema-driven ha-form for service data.
|
||||
*
|
||||
* Reuses the same login/panel-traversal pattern as
|
||||
* capture-readme-screenshots.mjs.
|
||||
*
|
||||
* docker compose --profile testing up -d # ensure playwright is up
|
||||
* node capture-v131-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) {
|
||||
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 } },
|
||||
});
|
||||
}
|
||||
});
|
||||
await page.goto(HA + "/maintenance-supporter");
|
||||
await page.waitForTimeout(8000);
|
||||
}
|
||||
|
||||
async function shot(page, name) {
|
||||
const filePath = path.join(OUTPUT, name);
|
||||
await page.screenshot({ path: filePath });
|
||||
console.log(` ${name}`);
|
||||
}
|
||||
|
||||
async function enableCompletionActionsFeature(page) {
|
||||
// Flip the feature flag via the WS settings update.
|
||||
const result = await page.evaluate(async () => {
|
||||
const ha = document.querySelector("home-assistant");
|
||||
const hass = ha && (ha.__hass || ha.hass);
|
||||
try {
|
||||
const r = await hass.connection.sendMessagePromise({
|
||||
type: "maintenance_supporter/global/update",
|
||||
settings: { advanced_completion_actions_visible: true },
|
||||
});
|
||||
return { ok: true, result: r };
|
||||
} catch (e) {
|
||||
return { ok: false, error: JSON.stringify(e) };
|
||||
}
|
||||
});
|
||||
console.log(" flag result:", JSON.stringify(result));
|
||||
// Reload to pick up the flag.
|
||||
await page.goto(HA + "/maintenance-supporter");
|
||||
await page.waitForTimeout(6000);
|
||||
}
|
||||
|
||||
async function getFirstTask(page) {
|
||||
const raw = await page.evaluate(`{
|
||||
${panelJS()}
|
||||
var result = null;
|
||||
if (p && p._objects && p._objects.length > 0) {
|
||||
for (var i = 0; i < p._objects.length; i++) {
|
||||
var obj = p._objects[i];
|
||||
if (obj.tasks && obj.tasks.length > 0) {
|
||||
result = { entry_id: obj.entry_id, task_id: obj.tasks[0].id, name: obj.tasks[0].name, obj_name: obj.object.name };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON.stringify(result);
|
||||
}`);
|
||||
const r = JSON.parse(raw);
|
||||
if (!r) throw new Error("No tasks found in demo data");
|
||||
return r;
|
||||
}
|
||||
|
||||
async function openEditDialog(page, entryId, taskId) {
|
||||
// Navigate to task detail first so the dialog has full context.
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) p._showTask('${entryId}', '${taskId}');
|
||||
}`);
|
||||
await page.waitForTimeout(2500);
|
||||
|
||||
// Open the edit dialog programmatically via the panel's task-dialog ref.
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) {
|
||||
var dlg = p.shadowRoot && p.shadowRoot.querySelector('maintenance-task-dialog');
|
||||
if (dlg) {
|
||||
// Find the task object from p._objects
|
||||
var task = null;
|
||||
for (var i = 0; i < p._objects.length; i++) {
|
||||
if (p._objects[i].entry_id === '${entryId}') {
|
||||
for (var j = 0; j < p._objects[i].tasks.length; j++) {
|
||||
if (p._objects[i].tasks[j].id === '${taskId}') {
|
||||
task = p._objects[i].tasks[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (task) dlg.openEdit('${entryId}', task);
|
||||
}
|
||||
}
|
||||
}`);
|
||||
await page.waitForTimeout(3000);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 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",
|
||||
});
|
||||
const page = await ctx.newPage();
|
||||
|
||||
console.log("Login + setup …");
|
||||
await login(page);
|
||||
await setEnglishDark(page);
|
||||
|
||||
console.log("Enable completion-actions feature flag …");
|
||||
await enableCompletionActionsFeature(page);
|
||||
|
||||
// ─── Settings tab — feature toggle ─────────────────────────────────────
|
||||
console.log("\nSettings tab screenshots:");
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) p._activeView = 'settings';
|
||||
if (p) p.requestUpdate();
|
||||
}`);
|
||||
await page.waitForTimeout(2000);
|
||||
|
||||
// Try to scroll to the Features section
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) {
|
||||
var sv = p.shadowRoot && p.shadowRoot.querySelector('maintenance-settings-view');
|
||||
if (sv && sv.shadowRoot) {
|
||||
var features = sv.shadowRoot.querySelector('.features-section, [data-section=features]');
|
||||
if (features) features.scrollIntoView({behavior: 'instant', block: 'start'});
|
||||
}
|
||||
}
|
||||
}`);
|
||||
await page.waitForTimeout(1500);
|
||||
await shot(page, "settings-completion-actions-toggle.png");
|
||||
|
||||
// ─── Open edit dialog with on_complete_action visible ─────────────────
|
||||
console.log("\nTask-dialog screenshots:");
|
||||
const t = await getFirstTask(page);
|
||||
console.log(` Using task: ${t.obj_name} → ${t.name}`);
|
||||
await openEditDialog(page, t.entry_id, t.task_id);
|
||||
|
||||
// Programmatically open the on_complete_action <details>
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) {
|
||||
var dlg = p.shadowRoot && p.shadowRoot.querySelector('maintenance-task-dialog');
|
||||
if (dlg && dlg.shadowRoot) {
|
||||
var detailsEls = dlg.shadowRoot.querySelectorAll('details.ca-section');
|
||||
detailsEls.forEach(function(d) { d.open = true; });
|
||||
// Scroll the first details into view
|
||||
if (detailsEls[0]) detailsEls[0].scrollIntoView({behavior: 'instant', block: 'center'});
|
||||
}
|
||||
}
|
||||
}`);
|
||||
await page.waitForTimeout(1500);
|
||||
await shot(page, "task-dialog-action-section-empty.png");
|
||||
|
||||
// Set service to light.turn_on so ha-form renders
|
||||
console.log(" Setting service = light.turn_on …");
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) {
|
||||
var dlg = p.shadowRoot && p.shadowRoot.querySelector('maintenance-task-dialog');
|
||||
if (dlg) {
|
||||
dlg._actionService = 'light.turn_on';
|
||||
dlg.requestUpdate();
|
||||
}
|
||||
}
|
||||
}`);
|
||||
await page.waitForTimeout(2000);
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) {
|
||||
var dlg = p.shadowRoot && p.shadowRoot.querySelector('maintenance-task-dialog');
|
||||
if (dlg && dlg.shadowRoot) {
|
||||
var d = dlg.shadowRoot.querySelector('details.ca-section');
|
||||
if (d) { d.open = true; d.scrollIntoView({behavior: 'instant', block: 'center'}); }
|
||||
}
|
||||
}
|
||||
}`);
|
||||
await page.waitForTimeout(1000);
|
||||
await shot(page, "task-dialog-action-form-light.png");
|
||||
|
||||
// Pick a service with no schema → JSON fallback
|
||||
console.log(" Setting service = button.press (no schema) …");
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) {
|
||||
var dlg = p.shadowRoot && p.shadowRoot.querySelector('maintenance-task-dialog');
|
||||
if (dlg) {
|
||||
dlg._actionService = 'button.press';
|
||||
dlg.requestUpdate();
|
||||
}
|
||||
}
|
||||
}`);
|
||||
await page.waitForTimeout(2000);
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) {
|
||||
var dlg = p.shadowRoot && p.shadowRoot.querySelector('maintenance-task-dialog');
|
||||
if (dlg && dlg.shadowRoot) {
|
||||
var d = dlg.shadowRoot.querySelector('details.ca-section');
|
||||
if (d) { d.open = true; d.scrollIntoView({behavior: 'instant', block: 'center'}); }
|
||||
}
|
||||
}
|
||||
}`);
|
||||
await page.waitForTimeout(1000);
|
||||
await shot(page, "task-dialog-action-form-fallback.png");
|
||||
|
||||
// Quick-complete defaults section
|
||||
console.log(" Quick-complete defaults section …");
|
||||
await page.evaluate(`{
|
||||
${panelJS()}
|
||||
if (p) {
|
||||
var dlg = p.shadowRoot && p.shadowRoot.querySelector('maintenance-task-dialog');
|
||||
if (dlg && dlg.shadowRoot) {
|
||||
var detailsEls = dlg.shadowRoot.querySelectorAll('details.ca-section');
|
||||
if (detailsEls[1]) {
|
||||
detailsEls[1].open = true;
|
||||
detailsEls[1].scrollIntoView({behavior: 'instant', block: 'center'});
|
||||
}
|
||||
}
|
||||
}
|
||||
}`);
|
||||
await page.waitForTimeout(1500);
|
||||
await shot(page, "task-dialog-quick-complete-defaults.png");
|
||||
|
||||
console.log("\nDone.");
|
||||
await browser.close();
|
||||
Reference in New Issue
Block a user