Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/thatdeveloperguy-website / tools/tdg-it-services-native-qa.mjs |
| Date | Not specified in source |
| Repository | Janady13/thatdeveloperguy-website |
| Revision | 2bea2ebdff0e34494bff597d4fa9c8422a0c3a94 |
| Branch | main |
| Path or archive member | tools/tdg-it-services-native-qa.mjs |
| Original SHA-256 | 5a4ca4cc470f2b4ffc0dba31bd01126ba5afd31fc4ab9a62840b33de23b5520a |
| Git blob SHA | 8bd3a0a12d8fde4edec9ad4cc213cf3d7285970d |
| Reading-copy SHA-256 | 5a4ca4cc470f2b4ffc0dba31bd01126ba5afd31fc4ab9a62840b33de23b5520a |
Recovered source transcript
Download complete source text ↓137 lines · 7.2 KB
import { chromium } from '@playwright/test';
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { PNG } from 'pngjs';
const baseUrl = process.env.TDG_QA_BASE ?? 'http://127.0.0.1:4411';
const outputDir = path.resolve(process.env.TDG_QA_OUT ?? 'evidence/rive/it-services/native-page');
await mkdir(outputDir, { recursive: true });
const browser = await chromium.launch({
channel: 'chrome',
headless: true,
args: ['--use-angle=metal', '--ignore-gpu-blocklist', '--enable-gpu-rasterization'],
});
const results = [];
const failures = [];
async function verifyViewport(name, viewport) {
const context = await browser.newContext({ viewport, deviceScaleFactor: 1 });
const page = await context.newPage();
const consoleErrors = [];
const requestFailures = [];
page.on('console', message => {
if (message.type() === 'error') consoleErrors.push(message.text());
});
page.on('requestfailed', request => {
requestFailures.push(`${request.url()} ${request.failure()?.errorText ?? ''}`);
});
await page.goto(`${baseUrl}/capabilities/business-it`, { waitUntil: 'networkidle' });
await page.waitForSelector('.scene[data-scene="it-services"]');
await page.waitForFunction(() => document.querySelector('.scene')?.getAttribute('data-native') !== 'loading');
const scene = page.locator('.scene[data-scene="it-services"]');
const nativeState = await scene.getAttribute('data-native');
if (nativeState !== 'ready') failures.push(`${name}: native Rive state was ${nativeState}`);
const interactions = [];
for (const hotspot of ['status', 'deployment', 'toolcart']) {
// Dispatch the same bubbling event React uses without asking Playwright to pan an
// overflow-hidden, cover-cropped mobile artboard to an offscreen SVG rectangle.
await page.locator(`.scene-hit[data-hit="${hotspot}"]`).dispatchEvent('mouseover');
await page.waitForTimeout(350);
const state = await scene.getAttribute('data-focus');
const nativeFocus = await page.locator('.it-services-rive').getAttribute('data-native-focus');
interactions.push({ hotspot, state, nativeFocus });
if (state !== hotspot || nativeFocus !== hotspot) {
failures.push(`${name}: ${hotspot} focus did not reach both DOM and native layers`);
}
}
await page.mouse.move(2, 2);
await page.waitForTimeout(300);
const screenshot = path.join(outputDir, `${name}.png`);
await page.screenshot({ path: screenshot, fullPage: false });
const pixels = PNG.sync.read(await readFile(screenshot));
let luma = 0;
for (let offset = 0; offset < pixels.data.length; offset += 4) {
luma += pixels.data[offset] * .2126 + pixels.data[offset + 1] * .7152 + pixels.data[offset + 2] * .0722;
}
const averageLuma = luma / (pixels.width * pixels.height);
const evidence = await page.evaluate(() => {
const sceneElement = document.querySelector('.scene[data-scene="it-services"]');
const plane = document.querySelector('.scene-plane');
const canvas = document.querySelector('.it-services-rive canvas');
const riveLayer = document.querySelector('.it-services-rive');
const poster = document.querySelector('.scene-poster');
const glCanvas = document.createElement('canvas');
const gl = glCanvas.getContext('webgl2');
const extension = gl?.getExtension('WEBGL_debug_renderer_info');
const rect = sceneElement?.getBoundingClientRect();
return {
renderer: extension && gl ? gl.getParameter(extension.UNMASKED_RENDERER_WEBGL) : 'unavailable',
native: sceneElement?.getAttribute('data-native'),
motion: sceneElement?.getAttribute('data-motion'),
rivePlaying: document.querySelector('.it-services-rive')?.getAttribute('data-playing'),
canvas: canvas ? { width: canvas.width, height: canvas.height } : null,
canvasRect: canvas ? { width: canvas.getBoundingClientRect().width, height: canvas.getBoundingClientRect().height, left: canvas.getBoundingClientRect().left, right: canvas.getBoundingClientRect().right } : null,
riveRect: riveLayer ? { width: riveLayer.getBoundingClientRect().width, height: riveLayer.getBoundingClientRect().height, left: riveLayer.getBoundingClientRect().left, right: riveLayer.getBoundingClientRect().right } : null,
bodyRect: { width: document.body.getBoundingClientRect().width, left: document.body.getBoundingClientRect().left, right: document.body.getBoundingClientRect().right },
posterVisibility: poster ? getComputedStyle(poster).visibility : null,
posterOpacity: poster ? getComputedStyle(poster).opacity : null,
sceneRect: rect ? { width: rect.width, height: rect.height, top: rect.top, left: rect.left } : null,
planeRect: plane ? { width: plane.getBoundingClientRect().width, height: plane.getBoundingClientRect().height, left: plane.getBoundingClientRect().left, right: plane.getBoundingClientRect().right, transform: getComputedStyle(plane).transform, cssLeft: getComputedStyle(plane).left, marginLeft: getComputedStyle(plane).marginLeft, offsetLeft: plane.offsetLeft, offsetParent: plane.offsetParent?.className ?? null } : null,
hotspots: document.querySelectorAll('.scene-hit').length,
bodyOverflow: getComputedStyle(document.body).overflow,
scroll: {
width: document.documentElement.scrollWidth,
height: document.documentElement.scrollHeight,
clientWidth: document.documentElement.clientWidth,
clientHeight: document.documentElement.clientHeight,
},
};
});
if (!evidence.canvas) failures.push(`${name}: native canvas missing`);
if (evidence.posterVisibility !== 'visible' || evidence.posterOpacity !== '1') failures.push(`${name}: authoritative room plate is not visible beneath native motion`);
if (averageLuma < 110) failures.push(`${name}: composed room is too dark (${averageLuma.toFixed(1)} average luma)`);
if (evidence.hotspots !== 6) failures.push(`${name}: expected 6 hotspots, found ${evidence.hotspots}`);
if (evidence.scroll.width > evidence.scroll.clientWidth + 1 || evidence.scroll.height > evidence.scroll.clientHeight + 1) {
failures.push(`${name}: page scrolls outside its full-viewport scene`);
}
if (consoleErrors.length) failures.push(`${name}: ${consoleErrors.length} console errors`);
if (requestFailures.length) failures.push(`${name}: ${requestFailures.length} failed requests`);
results.push({ name, viewport, screenshot, averageLuma, interactions, consoleErrors, requestFailures, evidence });
await context.close();
}
try {
await verifyViewport('desktop-1440x900', { width: 1440, height: 900 });
await verifyViewport('mobile-390x844', { width: 390, height: 844 });
} finally {
await browser.close();
}
const report = {
generatedAt: new Date().toISOString(),
baseUrl,
pass: failures.length === 0,
failures,
results,
};
await writeFile(path.join(outputDir, 'qa-results.json'), `${JSON.stringify(report, null, 2)}\n`);
for (const result of results) {
console.log(`${result.name}: ${result.evidence.native}, ${result.evidence.renderer}, ${result.evidence.hotspots} hotspots`);
}
if (failures.length) {
for (const failure of failures) console.error(`FAIL ${failure}`);
process.exitCode = 1;
} else {
console.log('PASS native IT Services page at desktop and mobile viewports');
}