Restore scratch setup bootstrap and live frontdoor sweep
This commit is contained in:
@@ -3,15 +3,25 @@ const { join } = require('path');
|
||||
|
||||
const linuxArchivePath = ['.cache', 'chromium', 'chrome-linux64', 'chrome'];
|
||||
const windowsArchivePath = ['.cache', 'chromium', 'chrome-win64', 'chrome.exe'];
|
||||
const macArchivePath = [
|
||||
'.cache',
|
||||
'chromium',
|
||||
'chrome-mac',
|
||||
'Chromium.app',
|
||||
'Contents',
|
||||
'MacOS',
|
||||
'Chromium'
|
||||
];
|
||||
const macArchivePath = [
|
||||
'.cache',
|
||||
'chromium',
|
||||
'chrome-mac',
|
||||
'Chromium.app',
|
||||
'Contents',
|
||||
'MacOS',
|
||||
'Chromium'
|
||||
];
|
||||
|
||||
function sortChromiumDirectories(entries) {
|
||||
return [...entries].sort((left, right) => {
|
||||
const leftMatch = /chromium-?(\d+)/i.exec(left);
|
||||
const rightMatch = /chromium-?(\d+)/i.exec(right);
|
||||
const leftValue = leftMatch ? Number.parseInt(leftMatch[1], 10) : Number.NEGATIVE_INFINITY;
|
||||
const rightValue = rightMatch ? Number.parseInt(rightMatch[1], 10) : Number.NEGATIVE_INFINITY;
|
||||
return rightValue - leftValue;
|
||||
});
|
||||
}
|
||||
|
||||
function expandVersionedArchives(rootDir = join(__dirname, '..')) {
|
||||
const base = join(rootDir, '.cache', 'chromium');
|
||||
@@ -91,19 +101,22 @@ function candidatePaths(rootDir = join(__dirname, '..')) {
|
||||
const { env } = process;
|
||||
const playwrightBase = join(rootDir, 'node_modules', 'playwright-core', '.local-browsers');
|
||||
const homePlaywrightBase = env.HOME ? join(env.HOME, '.cache', 'ms-playwright') : null;
|
||||
const windowsLocalPlaywrightBase = env.LOCALAPPDATA ? join(env.LOCALAPPDATA, 'ms-playwright') : null;
|
||||
const windowsProfilePlaywrightBase = env.USERPROFILE ? join(env.USERPROFILE, 'AppData', 'Local', 'ms-playwright') : null;
|
||||
const playwrightCacheBases = [homePlaywrightBase, windowsLocalPlaywrightBase, windowsProfilePlaywrightBase].filter(Boolean);
|
||||
let playwrightChromium = [];
|
||||
try {
|
||||
if (existsSync(playwrightBase)) {
|
||||
playwrightChromium = readdirSync(playwrightBase)
|
||||
.filter((d) => d.startsWith('chromium-'))
|
||||
const chromiumEntries = sortChromiumDirectories(
|
||||
readdirSync(playwrightBase).filter((d) => d.startsWith('chromium-'))
|
||||
);
|
||||
|
||||
playwrightChromium = chromiumEntries
|
||||
.map((d) => join(playwrightBase, d, 'chrome-linux', 'chrome'))
|
||||
.concat(
|
||||
readdirSync(playwrightBase)
|
||||
.filter((d) => d.startsWith('chromium-'))
|
||||
.map((d) => join(playwrightBase, d, 'chrome-win', 'chrome.exe')),
|
||||
readdirSync(playwrightBase)
|
||||
.filter((d) => d.startsWith('chromium-'))
|
||||
.map((d) => join(playwrightBase, d, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'))
|
||||
chromiumEntries.map((d) => join(playwrightBase, d, 'chrome-win', 'chrome.exe')),
|
||||
chromiumEntries.map((d) => join(playwrightBase, d, 'chrome-win64', 'chrome.exe')),
|
||||
chromiumEntries.map((d) => join(playwrightBase, d, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'))
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
@@ -112,14 +125,23 @@ function candidatePaths(rootDir = join(__dirname, '..')) {
|
||||
|
||||
let homeChromium = [];
|
||||
try {
|
||||
if (homePlaywrightBase && existsSync(homePlaywrightBase)) {
|
||||
homeChromium = readdirSync(homePlaywrightBase)
|
||||
.filter((d) => d.startsWith('chromium'))
|
||||
.flatMap((d) => [
|
||||
join(homePlaywrightBase, d, 'chrome-linux', 'chrome'),
|
||||
join(homePlaywrightBase, d, 'chrome-win', 'chrome.exe'),
|
||||
join(homePlaywrightBase, d, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'),
|
||||
]);
|
||||
for (const cacheBase of playwrightCacheBases) {
|
||||
if (!existsSync(cacheBase)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const chromiumEntries = sortChromiumDirectories(
|
||||
readdirSync(cacheBase).filter((d) => d.startsWith('chromium'))
|
||||
);
|
||||
|
||||
homeChromium.push(
|
||||
...chromiumEntries.flatMap((d) => [
|
||||
join(cacheBase, d, 'chrome-linux', 'chrome'),
|
||||
join(cacheBase, d, 'chrome-win', 'chrome.exe'),
|
||||
join(cacheBase, d, 'chrome-win64', 'chrome.exe'),
|
||||
join(cacheBase, d, 'chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'),
|
||||
])
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
homeChromium = [];
|
||||
|
||||
@@ -211,6 +211,37 @@ export async function authenticateFrontdoor(options = {}) {
|
||||
return report;
|
||||
}
|
||||
|
||||
export function getSessionStorageEntries(authReport) {
|
||||
return Array.isArray(authReport?.storage?.sessionStorageEntries)
|
||||
? authReport.storage.sessionStorageEntries.filter(
|
||||
(entry) => Array.isArray(entry) && typeof entry[0] === 'string' && typeof entry[1] === 'string',
|
||||
)
|
||||
: [];
|
||||
}
|
||||
|
||||
export async function addSessionStorageInitScript(context, authReport) {
|
||||
const sessionEntries = getSessionStorageEntries(authReport);
|
||||
await context.addInitScript((entries) => {
|
||||
sessionStorage.clear();
|
||||
for (const [key, value] of entries) {
|
||||
if (typeof key === 'string' && typeof value === 'string') {
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
}, sessionEntries);
|
||||
}
|
||||
|
||||
export async function createAuthenticatedContext(browser, authReport, options = {}) {
|
||||
const context = await browser.newContext({
|
||||
ignoreHTTPSErrors: true,
|
||||
storageState: options.statePath || DEFAULT_STATE_PATH,
|
||||
...options.contextOptions,
|
||||
});
|
||||
|
||||
await addSessionStorageInitScript(context, authReport);
|
||||
return context;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const report = await authenticateFrontdoor();
|
||||
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
import { authenticateFrontdoor, createAuthenticatedContext } from './live-frontdoor-auth.mjs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const webRoot = path.resolve(__dirname, '..');
|
||||
const outputDirectory = path.join(webRoot, 'output', 'playwright');
|
||||
|
||||
const BASE_URL = process.env.STELLAOPS_FRONTDOOR_BASE_URL?.trim() || 'https://stella-ops.local';
|
||||
const STATE_PATH = path.join(outputDirectory, 'live-frontdoor-auth-state.json');
|
||||
const REPORT_PATH = path.join(outputDirectory, 'live-frontdoor-auth-report.json');
|
||||
const RESULT_PATH = path.join(outputDirectory, 'live-frontdoor-canonical-route-sweep.json');
|
||||
|
||||
const scopedPrefixes = ['/mission-control', '/releases', '/security', '/evidence', '/ops'];
|
||||
const defaultScopeEntries = [
|
||||
['tenant', 'demo-prod'],
|
||||
['regions', 'us-east'],
|
||||
['environments', 'stage'],
|
||||
['timeWindow', '7d'],
|
||||
];
|
||||
|
||||
const canonicalRoutes = [
|
||||
'/mission-control/board',
|
||||
'/mission-control/alerts',
|
||||
'/mission-control/activity',
|
||||
'/releases',
|
||||
'/releases/overview',
|
||||
'/releases/versions',
|
||||
'/releases/versions/new',
|
||||
'/releases/runs',
|
||||
'/releases/approvals',
|
||||
'/releases/promotion-queue',
|
||||
'/releases/hotfixes',
|
||||
'/releases/hotfixes/new',
|
||||
'/releases/environments',
|
||||
'/releases/deployments',
|
||||
'/security',
|
||||
'/security/posture',
|
||||
'/security/triage',
|
||||
'/security/advisories-vex',
|
||||
'/security/disposition',
|
||||
'/security/supply-chain-data',
|
||||
'/security/supply-chain-data/graph',
|
||||
'/security/sbom-lake',
|
||||
'/security/reachability',
|
||||
'/security/reports',
|
||||
'/evidence',
|
||||
'/evidence/overview',
|
||||
'/evidence/capsules',
|
||||
'/evidence/verify-replay',
|
||||
'/evidence/proofs',
|
||||
'/evidence/exports',
|
||||
'/evidence/audit-log',
|
||||
'/ops',
|
||||
'/ops/operations',
|
||||
'/ops/operations/jobs-queues',
|
||||
'/ops/operations/feeds-airgap',
|
||||
'/ops/operations/data-integrity',
|
||||
'/ops/operations/system-health',
|
||||
'/ops/operations/health-slo',
|
||||
'/ops/operations/jobengine',
|
||||
'/ops/operations/scheduler',
|
||||
'/ops/operations/quotas',
|
||||
'/ops/operations/offline-kit',
|
||||
'/ops/operations/dead-letter',
|
||||
'/ops/operations/aoc',
|
||||
'/ops/operations/doctor',
|
||||
'/ops/operations/signals',
|
||||
'/ops/operations/packs',
|
||||
'/ops/operations/ai-runs',
|
||||
'/ops/operations/notifications',
|
||||
'/ops/operations/status',
|
||||
'/ops/integrations',
|
||||
'/ops/integrations/onboarding',
|
||||
'/ops/integrations/registries',
|
||||
'/ops/integrations/scm',
|
||||
'/ops/integrations/ci',
|
||||
'/ops/integrations/runtime-hosts',
|
||||
'/ops/integrations/advisory-vex-sources',
|
||||
'/ops/integrations/secrets',
|
||||
'/ops/integrations/notifications',
|
||||
'/ops/integrations/sbom-sources',
|
||||
'/ops/integrations/activity',
|
||||
'/ops/policy',
|
||||
'/ops/policy/overview',
|
||||
'/ops/policy/baselines',
|
||||
'/ops/policy/gates',
|
||||
'/ops/policy/simulation',
|
||||
'/ops/policy/waivers',
|
||||
'/ops/policy/risk-budget',
|
||||
'/ops/policy/trust-weights',
|
||||
'/ops/policy/staleness',
|
||||
'/ops/policy/sealed-mode',
|
||||
'/ops/policy/profiles',
|
||||
'/ops/policy/validator',
|
||||
'/ops/policy/audit',
|
||||
'/ops/platform-setup',
|
||||
'/ops/platform-setup/regions-environments',
|
||||
'/ops/platform-setup/promotion-paths',
|
||||
'/ops/platform-setup/workflows-gates',
|
||||
'/ops/platform-setup/release-templates',
|
||||
'/ops/platform-setup/policy-bindings',
|
||||
'/ops/platform-setup/gate-profiles',
|
||||
'/ops/platform-setup/defaults-guardrails',
|
||||
'/ops/platform-setup/trust-signing',
|
||||
'/setup',
|
||||
'/setup/integrations',
|
||||
'/setup/integrations/advisory-vex-sources',
|
||||
'/setup/integrations/secrets',
|
||||
'/setup/identity-access',
|
||||
'/setup/tenant-branding',
|
||||
'/setup/notifications',
|
||||
'/setup/usage',
|
||||
'/setup/system',
|
||||
'/setup/trust-signing',
|
||||
'/setup/topology',
|
||||
'/setup/topology/overview',
|
||||
'/setup/topology/map',
|
||||
'/setup/topology/regions',
|
||||
'/setup/topology/environments',
|
||||
'/setup/topology/targets',
|
||||
'/setup/topology/hosts',
|
||||
'/setup/topology/agents',
|
||||
'/setup/topology/connectivity',
|
||||
'/setup/topology/runtime-drift',
|
||||
'/setup/topology/promotion-graph',
|
||||
'/setup/topology/workflows',
|
||||
'/setup/topology/gate-profiles',
|
||||
];
|
||||
|
||||
const strictRouteExpectations = {
|
||||
'/security/advisories-vex': {
|
||||
title: /Advisories/i,
|
||||
texts: ['Security / Advisories & VEX', 'Providers'],
|
||||
},
|
||||
'/security/sbom-lake': {
|
||||
title: /SBOM Lake/i,
|
||||
texts: ['SBOM Lake', 'Attestation Coverage Metrics'],
|
||||
},
|
||||
'/security/reachability': {
|
||||
title: /Reachability/i,
|
||||
texts: ['Reachability', 'Proof of Exposure'],
|
||||
},
|
||||
'/setup/trust-signing': {
|
||||
title: /Trust/i,
|
||||
texts: ['Trust Management'],
|
||||
},
|
||||
'/setup/integrations': {
|
||||
title: /Integrations/i,
|
||||
texts: ['Integrations', 'External system connectors'],
|
||||
},
|
||||
'/setup/integrations/advisory-vex-sources': {
|
||||
title: /Advisory & VEX Sources/i,
|
||||
texts: ['Integrations', 'FeedMirror Integrations'],
|
||||
},
|
||||
'/setup/integrations/secrets': {
|
||||
title: /Secrets/i,
|
||||
texts: ['Integrations', 'RepoSource Integrations'],
|
||||
},
|
||||
'/ops/policy': {
|
||||
title: /Policy/i,
|
||||
texts: ['Policy Decisioning Studio', 'One operator shell for policy, VEX, and release gates'],
|
||||
},
|
||||
'/ops/policy/overview': {
|
||||
title: /Policy/i,
|
||||
texts: ['Policy Decisioning Studio', 'One operator shell for policy, VEX, and release gates'],
|
||||
},
|
||||
'/ops/policy/risk-budget': {
|
||||
title: /Policy/i,
|
||||
texts: ['Policy Decisioning Studio', 'Risk Budget Overview'],
|
||||
},
|
||||
};
|
||||
|
||||
const allowedFinalPaths = {
|
||||
'/releases': ['/releases/deployments'],
|
||||
'/releases/promotion-queue': ['/releases/promotions'],
|
||||
'/ops/policy': ['/ops/policy/overview'],
|
||||
'/ops/policy/audit': ['/ops/policy/audit/policy'],
|
||||
'/ops/platform-setup/trust-signing': ['/setup/trust-signing'],
|
||||
'/setup/topology': ['/setup/topology/overview'],
|
||||
};
|
||||
|
||||
function buildRouteUrl(routePath) {
|
||||
const url = new URL(routePath, BASE_URL);
|
||||
if (scopedPrefixes.some((prefix) => routePath.startsWith(prefix))) {
|
||||
for (const [key, value] of defaultScopeEntries) {
|
||||
if (!url.searchParams.has(key)) {
|
||||
url.searchParams.set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function trimText(value, maxLength = 300) {
|
||||
const normalized = value.replace(/\s+/g, ' ').trim();
|
||||
return normalized.length > maxLength ? `${normalized.slice(0, maxLength)}...` : normalized;
|
||||
}
|
||||
|
||||
function shouldIgnoreUrl(url) {
|
||||
return /\.(?:css|js|map|png|jpg|jpeg|svg|woff2?)(?:$|\?)/i.test(url) || url.startsWith('data:');
|
||||
}
|
||||
|
||||
async function collectHeadings(page) {
|
||||
return page.locator('h1, main h1, main h2, h2').evaluateAll((elements) =>
|
||||
elements
|
||||
.map((element) => (element.textContent || '').replace(/\s+/g, ' ').trim())
|
||||
.filter((text, index, values) => text.length > 0 && values.indexOf(text) === index),
|
||||
).catch(() => []);
|
||||
}
|
||||
|
||||
async function collectVisibleProblemTexts(page) {
|
||||
return page.locator([
|
||||
'[role="alert"]',
|
||||
'.alert',
|
||||
'.banner',
|
||||
'.status-banner',
|
||||
'.degraded-banner',
|
||||
'.warning-banner',
|
||||
'.error-banner',
|
||||
'.empty-state',
|
||||
'.error-state',
|
||||
].join(', ')).evaluateAll((elements) =>
|
||||
elements
|
||||
.map((element) => (element.textContent || '').replace(/\s+/g, ' ').trim())
|
||||
.filter((text) =>
|
||||
text.length > 0 && /(failed|unable|error|warning|degraded|unavailable|timed out|timeout|no results)/i.test(text),
|
||||
),
|
||||
).catch(() => []);
|
||||
}
|
||||
|
||||
async function collectVisibleActions(page) {
|
||||
return page.locator('main a[href], main button').evaluateAll((elements) =>
|
||||
elements
|
||||
.map((element) => {
|
||||
const tagName = element.tagName.toLowerCase();
|
||||
const text = (element.textContent || '').replace(/\s+/g, ' ').trim();
|
||||
const href = tagName === 'a' ? element.getAttribute('href') || '' : '';
|
||||
return {
|
||||
tagName,
|
||||
text,
|
||||
href,
|
||||
};
|
||||
})
|
||||
.filter((entry) => entry.text.length > 0 || entry.href.length > 0)
|
||||
.slice(0, 20),
|
||||
).catch(() => []);
|
||||
}
|
||||
|
||||
function collectExpectationFailures(routePath, title, headings, bodyText) {
|
||||
const expectation = strictRouteExpectations[routePath];
|
||||
if (!expectation) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const failures = [];
|
||||
if (!expectation.title.test(title)) {
|
||||
failures.push(`title mismatch: expected ${expectation.title}`);
|
||||
}
|
||||
|
||||
for (const text of expectation.texts) {
|
||||
const present = headings.some((heading) => heading.includes(text)) || bodyText.includes(text);
|
||||
if (!present) {
|
||||
failures.push(`missing text: ${text}`);
|
||||
}
|
||||
}
|
||||
|
||||
return failures;
|
||||
}
|
||||
|
||||
function finalPathMatchesRoute(routePath, finalUrl) {
|
||||
const finalPath = new URL(finalUrl).pathname;
|
||||
if (finalPath === routePath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return allowedFinalPaths[routePath]?.includes(finalPath) ?? false;
|
||||
}
|
||||
|
||||
async function inspectRoute(context, routePath) {
|
||||
const page = await context.newPage();
|
||||
const consoleErrors = [];
|
||||
const requestFailures = [];
|
||||
const responseErrors = [];
|
||||
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'error') {
|
||||
consoleErrors.push(trimText(message.text(), 500));
|
||||
}
|
||||
});
|
||||
|
||||
page.on('requestfailed', (request) => {
|
||||
const url = request.url();
|
||||
if (shouldIgnoreUrl(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestFailures.push({
|
||||
method: request.method(),
|
||||
url,
|
||||
error: request.failure()?.errorText ?? 'unknown',
|
||||
});
|
||||
});
|
||||
|
||||
page.on('response', (response) => {
|
||||
const url = response.url();
|
||||
if (shouldIgnoreUrl(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status() >= 400) {
|
||||
responseErrors.push({
|
||||
status: response.status(),
|
||||
method: response.request().method(),
|
||||
url,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const targetUrl = buildRouteUrl(routePath);
|
||||
await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
||||
await page.waitForLoadState('networkidle', { timeout: 15_000 }).catch(() => {});
|
||||
await page.waitForTimeout(1_500);
|
||||
|
||||
const finalUrl = page.url();
|
||||
const title = await page.title().catch(() => '');
|
||||
const headings = await collectHeadings(page);
|
||||
const bodyText = trimText(await page.locator('body').innerText().catch(() => ''), 1_200);
|
||||
const problemTexts = await collectVisibleProblemTexts(page);
|
||||
const visibleActions = await collectVisibleActions(page);
|
||||
|
||||
const finalPathMatches = finalPathMatchesRoute(routePath, finalUrl);
|
||||
const expectationFailures = collectExpectationFailures(routePath, title, headings, bodyText);
|
||||
|
||||
const record = {
|
||||
routePath,
|
||||
targetUrl,
|
||||
finalUrl,
|
||||
title,
|
||||
headings,
|
||||
problemTexts,
|
||||
visibleActions,
|
||||
consoleErrors,
|
||||
requestFailures,
|
||||
responseErrors,
|
||||
finalPathMatches,
|
||||
expectationFailures,
|
||||
passed: finalPathMatches
|
||||
&& expectationFailures.length === 0
|
||||
&& consoleErrors.length === 0
|
||||
&& requestFailures.length === 0
|
||||
&& responseErrors.length === 0
|
||||
&& problemTexts.length === 0,
|
||||
};
|
||||
|
||||
await page.close();
|
||||
return record;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
mkdirSync(outputDirectory, { recursive: true });
|
||||
|
||||
await authenticateFrontdoor({
|
||||
baseUrl: BASE_URL,
|
||||
statePath: STATE_PATH,
|
||||
reportPath: REPORT_PATH,
|
||||
headless: true,
|
||||
});
|
||||
|
||||
const authReport = JSON.parse(readFileSync(REPORT_PATH, 'utf8'));
|
||||
const browser = await chromium.launch({ headless: true, args: ['--disable-dev-shm-usage'] });
|
||||
|
||||
try {
|
||||
const context = await createAuthenticatedContext(browser, authReport, { statePath: STATE_PATH });
|
||||
const routes = [];
|
||||
|
||||
for (const routePath of canonicalRoutes) {
|
||||
const record = await inspectRoute(context, routePath);
|
||||
routes.push(record);
|
||||
const status = record.passed ? 'PASS' : 'FAIL';
|
||||
process.stdout.write(`[live-frontdoor-canonical-route-sweep] ${status} ${routePath} -> ${record.finalUrl}\n`);
|
||||
}
|
||||
|
||||
await context.close();
|
||||
|
||||
const summary = {
|
||||
checkedAtUtc: new Date().toISOString(),
|
||||
baseUrl: BASE_URL,
|
||||
totalRoutes: routes.length,
|
||||
passedRoutes: routes.filter((route) => route.passed).length,
|
||||
failedRoutes: routes.filter((route) => !route.passed).map((route) => route.routePath),
|
||||
routes,
|
||||
};
|
||||
|
||||
writeFileSync(RESULT_PATH, `${JSON.stringify(summary, null, 2)}\n`, 'utf8');
|
||||
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
||||
|
||||
if (summary.failedRoutes.length > 0) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`[live-frontdoor-canonical-route-sweep] ${error instanceof Error ? error.message : String(error)}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
import { authenticateFrontdoor } from './live-frontdoor-auth.mjs';
|
||||
import { authenticateFrontdoor, createAuthenticatedContext } from './live-frontdoor-auth.mjs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -417,20 +417,7 @@ async function main() {
|
||||
headless: true,
|
||||
args: ['--disable-dev-shm-usage'],
|
||||
});
|
||||
const context = await browser.newContext({
|
||||
ignoreHTTPSErrors: true,
|
||||
storageState: statePath,
|
||||
});
|
||||
const sessionEntries = Array.isArray(authReport.storage?.sessionStorageEntries)
|
||||
? authReport.storage.sessionStorageEntries
|
||||
: [];
|
||||
await context.addInitScript((entries) => {
|
||||
for (const [key, value] of entries) {
|
||||
if (typeof key === 'string' && typeof value === 'string') {
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
}, sessionEntries);
|
||||
const context = await createAuthenticatedContext(browser, authReport, { statePath });
|
||||
|
||||
const report = {
|
||||
generatedAtUtc: new Date().toISOString(),
|
||||
|
||||
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
import { authenticateFrontdoor } from './live-frontdoor-auth.mjs';
|
||||
import { authenticateFrontdoor, createAuthenticatedContext } from './live-frontdoor-auth.mjs';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -21,23 +21,14 @@ const REPORT_PATH = path.join(outputDirectory, 'live-frontdoor-auth-report.json'
|
||||
const RESULT_PATH = path.join(outputDirectory, 'live-releases-deployments-check.json');
|
||||
|
||||
async function seedAuthenticatedPage(browser, authReport) {
|
||||
const context = await browser.newContext({
|
||||
ignoreHTTPSErrors: true,
|
||||
acceptDownloads: true,
|
||||
storageState: STATE_PATH,
|
||||
const context = await createAuthenticatedContext(browser, authReport, {
|
||||
statePath: STATE_PATH,
|
||||
contextOptions: {
|
||||
acceptDownloads: true,
|
||||
},
|
||||
});
|
||||
const page = await context.newPage();
|
||||
|
||||
await page.goto(`${BASE_URL}/welcome`, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
||||
await page.evaluate((storage) => {
|
||||
sessionStorage.clear();
|
||||
for (const [key, value] of storage.sessionStorageEntries ?? []) {
|
||||
if (typeof key === 'string' && typeof value === 'string') {
|
||||
sessionStorage.setItem(key, value);
|
||||
}
|
||||
}
|
||||
}, authReport.storage);
|
||||
|
||||
await page.goto(LIST_URL, { waitUntil: 'networkidle', timeout: 30_000 });
|
||||
return { context, page };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user