Check the P1/A1 camera in real Chrome
BROWSER=1 on e2e-camera-snapshot.sh signs in with the real Supabase test user, opens the fleet page and checks the snapshot view decodes, refreshes and has picture content.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* The P1/A1 camera in the real web console, in a real Chrome, for
|
||||
* scripts/e2e-camera-snapshot.sh (BROWSER=1). Signs in the same way as
|
||||
* e2e-camera-browser.ts. Reads pixel statistics and keeps no screenshot: the
|
||||
* frame is the inside of someone's workshop.
|
||||
*
|
||||
* Results are `ok<TAB>label` / `FAILED<TAB>label`.
|
||||
* Environment: APP_URL, SUPABASE_E2E_EMAIL, SUPABASE_E2E_PASSWORD, CHROME_BIN.
|
||||
*/
|
||||
import puppeteer from "puppeteer-core";
|
||||
|
||||
const appUrl = process.env.APP_URL!;
|
||||
const email = process.env.SUPABASE_E2E_EMAIL!;
|
||||
const password = process.env.SUPABASE_E2E_PASSWORD!;
|
||||
const chromeBin = process.env.CHROME_BIN ?? "/usr/bin/google-chrome";
|
||||
|
||||
let failed = false;
|
||||
const say = (good: boolean, label: string, detail = "") => {
|
||||
if (!good) failed = true;
|
||||
console.log(`${good ? "ok" : "FAILED"}\t${label}${detail ? ` (${detail})` : ""}`);
|
||||
};
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
executablePath: chromeBin,
|
||||
headless: true,
|
||||
args: ["--no-sandbox", "--disable-dev-shm-usage"],
|
||||
});
|
||||
|
||||
try {
|
||||
const page = await browser.newPage();
|
||||
const pageErrors: string[] = [];
|
||||
page.on("pageerror", (err) => pageErrors.push(String((err as Error).message ?? err).slice(0, 160)));
|
||||
|
||||
await page.goto(`${appUrl}/login`, { waitUntil: "domcontentloaded", timeout: 120_000 });
|
||||
await page.waitForFunction(
|
||||
() => typeof (window as any).useNuxtApp === "function" && Boolean((window as any).useNuxtApp().$supabase),
|
||||
{ timeout: 120_000 },
|
||||
);
|
||||
const signInError = await page.evaluate(
|
||||
async (e: string, p: string) => {
|
||||
const { client } = (window as any).useNuxtApp().$supabase;
|
||||
const { error } = await client.auth.signInWithPassword({ email: e, password: p });
|
||||
return error ? String(error.message) : null;
|
||||
},
|
||||
email,
|
||||
password,
|
||||
);
|
||||
say(signInError === null, "the app's own Supabase client signs the user in", signInError ?? "");
|
||||
if (signInError) process.exit(1);
|
||||
|
||||
await page.goto(`${appUrl}/`, { waitUntil: "domcontentloaded", timeout: 60_000 });
|
||||
const stayed = await page.waitForSelector(".fleet", { timeout: 60_000 }).then(() => true).catch(() => false);
|
||||
say(stayed && !page.url().includes("/login"), "the fleet page loads and does not bounce back to /login");
|
||||
|
||||
const live = await page
|
||||
.waitForFunction(() => document.querySelector(".app-shell__status.is-online") !== null, { timeout: 30_000 })
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
say(live, "the header reports Live");
|
||||
|
||||
const shown = await page
|
||||
.waitForFunction(() => document.querySelector(".snapshot-view__status.is-connected") !== null, { timeout: 60_000 })
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
const status = await page.evaluate(
|
||||
() => document.querySelector(".snapshot-view__status")?.textContent?.trim() ?? "no snapshot view rendered",
|
||||
);
|
||||
say(shown, "the snapshot view reports connected", `status: ${status}`);
|
||||
say(
|
||||
(await page.$(".webrtc-view")) === null,
|
||||
"no WebRTC player is rendered for a snapshot camera",
|
||||
);
|
||||
|
||||
if (shown) {
|
||||
// Two different frames, a few seconds apart: the picture is refreshing.
|
||||
const result = await page.evaluate(async () => {
|
||||
const img = document.querySelector<HTMLImageElement>(".snapshot-view img")!;
|
||||
const first = img.src;
|
||||
const t0 = Date.now();
|
||||
while (img.src === first && Date.now() - t0 < 15_000) await new Promise((r) => setTimeout(r, 250));
|
||||
const refreshed = img.src !== first;
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = 64;
|
||||
canvas.height = 36;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
const px = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
||||
let sum = 0;
|
||||
let sumSq = 0;
|
||||
const n = px.length / 4;
|
||||
for (let i = 0; i < px.length; i += 4) {
|
||||
const y = 0.299 * px[i] + 0.587 * px[i + 1] + 0.114 * px[i + 2];
|
||||
sum += y;
|
||||
sumSq += y * y;
|
||||
}
|
||||
const mean = sum / n;
|
||||
return {
|
||||
refreshed,
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
mean,
|
||||
stdev: Math.sqrt(Math.max(0, sumSq / n - mean * mean)),
|
||||
};
|
||||
});
|
||||
say(result.width > 0, "Chrome decoded the JPEG", `${result.width}x${result.height}`);
|
||||
say(result.refreshed, "the picture refreshes by itself");
|
||||
say(result.stdev > 2, "the frame has picture content, not black or one flat colour",
|
||||
`mean luma ${result.mean.toFixed(0)}, stdev ${result.stdev.toFixed(1)}`);
|
||||
}
|
||||
|
||||
say(pageErrors.length === 0, "the page raised no uncaught errors", pageErrors.slice(0, 2).join(" | "));
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
process.exit(failed ? 1 : 0);
|
||||
Reference in New Issue
Block a user