Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ build --action_env=NODE_OPTIONS=--max-old-space-size=4096

test --test_output=errors
test --test_timeout=120,600,1800,3600
test --test_env=GF_RBE_CHROMIUM_EXECUTABLE=/bin/chromium

build:local --disk_cache=~/.cache/bazel-jesssullivan-github-io

Expand Down
30 changes: 30 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ filegroup(
],
)

filegroup(
name = "browser_smoke_srcs",
srcs = [
"package.json",
"scripts/run-playwright-chromium-bazel.mjs",
],
)

js_binary(
name = "vitest_bazel",
data = [
Expand Down Expand Up @@ -57,11 +65,33 @@ js_test(
visibility = ["//visibility:public"],
)

js_test(
name = "playwright_chromium_smoke",
copy_data_to_bin = False,
data = [
":browser_smoke_srcs",
":node_modules",
],
entry_point = "scripts/run-playwright-chromium-bazel.mjs",
patch_node_fs = False,
tags = [
"chromium",
"gloriousflywheel-rbe-candidate",
"playwright",
"sveltekit",
"vite",
"web-browser-runtime-authority",
],
timeout = "moderate",
visibility = ["//visibility:public"],
)

exports_files(
[
"package-lock.json",
"package.json",
"pnpm-lock.yaml",
"scripts/run-playwright-chromium-bazel.mjs",
"scripts/run-vitest-bazel.mjs",
"vitest.bazel.config.ts",
],
Expand Down
3 changes: 3 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ npm.npm_translate_lock(
"//packages/pulse-core:package.json",
],
lifecycle_hooks_envs = {
"playwright": [
"PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1",
],
"puppeteer": [
"PUPPETEER_SKIP_DOWNLOAD=true",
"PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true",
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@ flowchart LR
This repo still uses the npm/SvelteKit workflow for normal local development and deployment. The Bazel files are a narrow GloriousFlywheel consumer proof surface, not a wholesale migration of the blog build.

- `//:types_unit_tests` wraps Vitest through `vitest.bazel.config.ts` and runs the existing `src/lib/types.test.ts` slice.
- `//:playwright_chromium_smoke` launches Playwright against the pinned GloriousFlywheel Chromium runtime path. It is a browser-runtime smoke target, not the full hosted Playwright regression suite.
- `package-lock.json` remains the npm dependency authority for the app. `pnpm-lock.yaml` is the generated `rules_js` lock consumed by Bazel.
- Bazel npm lifecycle hooks skip Puppeteer browser downloads; browser-backed Playwright/Puppeteer RBE needs a separate pinned browser/toolchain tranche.
- Bazel npm lifecycle hooks skip Playwright and Puppeteer browser downloads. Browser-backed RBE must use the pinned worker Chromium path rather than downloading browsers during proof actions.
- GloriousFlywheel proof runs should use the external GF REAPI proof harness against this public repo checkout.

Current boundary: this proves one public SvelteKit/Vite/Vitest target class for remote execution evidence. It does not prove default repo-wide RBE, Playwright/Puppeteer browser execution, the full SvelteKit build, or deployment.
Current boundary: this proves narrow public SvelteKit/Vite/Vitest and Playwright/Chromium target classes for remote execution evidence. It does not prove default repo-wide RBE, the full hosted Playwright suite, Puppeteer browser execution, the full SvelteKit build, or deployment.



Expand Down
138 changes: 138 additions & 0 deletions scripts/run-playwright-chromium-bazel.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, statSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { chromium } from '@playwright/test';

const runtimeDir = mkdtempSync(join(tmpdir(), 'ghio-playwright-chromium-'));
ensureWritableEnvDir('HOME', join(runtimeDir, 'home'));
ensureWritableEnvDir('XDG_CONFIG_HOME', join(runtimeDir, 'xdg-config'));
ensureWritableEnvDir('XDG_CACHE_HOME', join(runtimeDir, 'xdg-cache'));

const chromiumExecutable = findChromiumExecutable();
if (!chromiumExecutable) {
console.error(
'No Chromium executable found. Set GF_RBE_CHROMIUM_EXECUTABLE, PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, or CHROME_BIN.',
);
process.exit(1);
}

const packageJson = JSON.parse(await readFile('package.json', 'utf8'));

let browser;
let page;
try {
browser = await chromium.launch({
executablePath: chromiumExecutable,
headless: true,
args: ['--disable-dev-shm-usage', '--no-sandbox'],
});
page = await browser.newPage({
deviceScaleFactor: 1,
viewport: { width: 1024, height: 640 },
});
await page.setContent(renderSmokePage(packageJson.name), { waitUntil: 'load' });

const title = await page.locator('main h1').textContent();
const target = await page.locator('[data-rbe-target]').textContent();
if (title !== 'GloriousFlywheel browser RBE smoke') {
throw new Error(`unexpected smoke title: ${title}`);
}
if (target !== packageJson.name) {
throw new Error(`unexpected package marker: ${target}`);
}

console.log(`Playwright Chromium smoke passed with ${chromiumExecutable}`);
} catch (error) {
if (page) {
await printPageDiagnostics(page);
}
throw error;
} finally {
await browser?.close();
}

function renderSmokePage(packageName) {
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Browser RBE smoke</title>
</head>
<body>
<main>
<h1>GloriousFlywheel browser RBE smoke</h1>
<p data-rbe-target>${escapeHtml(packageName)}</p>
</main>
</body>
</html>`;
}

function findChromiumExecutable() {
const candidates = [
process.env.GF_RBE_CHROMIUM_EXECUTABLE,
process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH,
process.env.CHROME_BIN,
'/bin/chromium',
'/usr/bin/chromium',
'/usr/bin/chromium-browser',
'/usr/bin/google-chrome',
'/usr/bin/google-chrome-stable',
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Chromium.app/Contents/MacOS/Chromium',
].filter(Boolean);

for (const candidate of candidates) {
if (existsSync(candidate)) {
return candidate;
}
}

return '';
}

function ensureWritableEnvDir(name, fallback) {
const current = process.env[name];
if (current && isWritableDirectory(current)) {
return current;
}

mkdirSync(fallback, { recursive: true });
process.env[name] = fallback;
return fallback;
}

function isWritableDirectory(path) {
try {
if (!existsSync(path) || !statSync(path).isDirectory()) {
return false;
}
accessSync(path, constants.W_OK);
return true;
} catch {
return false;
}
}

function escapeHtml(value) {
return String(value)
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}

async function printPageDiagnostics(page) {
const diagnostics = {
url: page.url(),
title: await page.title().catch(() => ''),
viewport: page.viewportSize(),
mainText: await page
.locator('main')
.textContent({ timeout: 1000 })
.then((text) => text?.slice(0, 240))
.catch(() => null),
};
console.error(`Playwright Chromium smoke diagnostics: ${JSON.stringify(diagnostics, null, 2)}`);
}
Loading