Skip to content
Open
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
51 changes: 51 additions & 0 deletions packages/backend.ai-webui-smoke-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,54 @@ Post-install smoke verification CLI for Backend.AI WebUI. See
[`.specs/FR-2871-webui-smoke-cli/spec.md`](../../.specs/FR-2871-webui-smoke-cli/spec.md)
for the full spec. The operator-facing README (EN + KO) lands in FR-2883
(Phase 2).

## Usage (alpha)

Prefer reading the password from stdin or an env var — passing
`--password` on the command line exposes the secret to anything that
can read the host process's argv. The CLI scrubs the argv value at
startup, but the original invocation may still surface in shell history
or process listings before the scrub runs.

```sh
# Option 1 — password from stdin (recommended)
printf '%s' "$BAI_PASSWORD" | bai-smoke run \
--endpoint https://webui.example.com \
--webserver https://webui.example.com \
--email admin@example.com \
--password-stdin \
--output ./smoke-report

# Option 2 — password from env var
BAI_SMOKE_PASSWORD="$BAI_PASSWORD" bai-smoke run \
--endpoint https://webui.example.com \
--webserver https://webui.example.com \
--email admin@example.com \
--output ./smoke-report
```

To widen the selection beyond the default `@smoke*` set, use
`--also-include`. To narrow the run, use `--pages`:

```sh
bai-smoke run \
--endpoint https://webui.example.com \
--email admin@example.com \
--password-stdin \
--also-include "@critical" \
--pages session,vfolder
```

## Limitations (alpha MVP)

- Must be run from a `backend.ai-webui` monorepo checkout — the e2e
specs are not bundled yet. Tarball / single-binary distribution is
tracked in FR-2881.
- Air-gap binary, `doctor` command, and rich diagnostic reports → Phase 2.
- `--also-include` widens the smoke set; to narrow it, use `--pages`
instead. The legacy `--include` flag is a deprecated hidden alias and
will be removed in a future release.
- `--insecure-tls` accepts self-signed certs but does not pin
fingerprints — only enable on trusted networks.

This is an alpha MVP. Full operator docs (EN + KO) land with FR-2883.
2 changes: 2 additions & 0 deletions packages/backend.ai-webui-smoke-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"files": [
"bin",
"dist",
"playwright.smoke.config.ts",
"README.md"
],
"scripts": {
Expand All @@ -30,6 +31,7 @@
"version:print": "node ./bin/bai-smoke.cjs version"
},
"dependencies": {
"@playwright/test": "^1.58.2",
"commander": "^12.1.0"
},
"devDependencies": {
Expand Down
101 changes: 101 additions & 0 deletions packages/backend.ai-webui-smoke-cli/playwright.smoke.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { defineConfig, devices } from '@playwright/test';
import { existsSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

/**
* Playwright configuration for the `bai-smoke` runner.
*
* This config is invoked by `bai-smoke run` (`src/runner.ts`), which
* spawns Node directly with the resolved `@playwright/test` CLI
* entrypoint and passes `--config <this-file>`. No `npx` is involved.
* The runner is the single
* place that translates CLI arguments into the env vars consumed here —
* we deliberately do NOT load `e2e/envs/.env.playwright` from this file
* because the smoke CLI runs against arbitrary customer endpoints, not
* the dev fixtures the e2e suite assumes.
*
* Env contract (set by the runner):
* BAI_SMOKE_REPORT_DIR — output directory for html / json reports
* BAI_SMOKE_WORKERS — playwright worker count (optional)
* BAI_SMOKE_TIMEOUT_MS — per-test timeout in ms (default 180000)
* BAI_SMOKE_GREP — grep regex source (no slashes), e.g. `@smoke\b|@smoke-admin\b`
* BAI_SMOKE_GREP_INVERT — grepInvert regex source (optional)
* BAI_SMOKE_PAGES — comma-separated page directory names (optional)
* BAI_SMOKE_HEADED — '1' to launch a headed browser
*
* Test credentials (E2E_*) are set by the runner as well — see
* `src/config.ts#buildPlaywrightEnv`.
*/

// Resolve repo root e2e/ directory relative to this config file.
// File layout: <repo>/packages/backend.ai-webui-smoke-cli/playwright.smoke.config.ts
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const E2E_DIR = path.resolve(__dirname, '..', '..', 'e2e');

// FR-2877 MVP limitation: the smoke runner discovers specs from the
// monorepo's e2e/ tree. A bundled tarball / standalone binary distribution
// is tracked in FR-2881. Fail loudly when the directory is missing so the
// user gets a clear message instead of a cryptic Playwright "no tests
// found" output.
if (!existsSync(E2E_DIR)) {
throw new Error(
`bai-smoke MVP requires running from a backend.ai-webui checkout. Expected e2e dir at: ${E2E_DIR}. ` +
`Tarball / single-binary distribution lands in FR-2881.`,
);
}

const reportDir = process.env.BAI_SMOKE_REPORT_DIR ?? path.resolve(process.cwd(), 'smoke-report');

const workersEnv = process.env.BAI_SMOKE_WORKERS;
const workers = workersEnv ? Number.parseInt(workersEnv, 10) : undefined;

const timeoutEnv = process.env.BAI_SMOKE_TIMEOUT_MS;
const timeout = timeoutEnv ? Number.parseInt(timeoutEnv, 10) : 180000;

const grepSource = process.env.BAI_SMOKE_GREP;
const grepInvertSource = process.env.BAI_SMOKE_GREP_INVERT;

// Pages filter — match by directory under e2e/. e.g. BAI_SMOKE_PAGES="session,vfolder"
// turns into testMatch ['**/session/**', '**/vfolder/**'].
const pagesEnv = process.env.BAI_SMOKE_PAGES;
const pages = pagesEnv
? pagesEnv
.split(',')
.map((p) => p.trim())
.filter(Boolean)
: undefined;
const testMatch = pages && pages.length > 0 ? pages.map((p) => `**/${p}/**`) : undefined;

const headed = process.env.BAI_SMOKE_HEADED === '1';

export default defineConfig({
testDir: E2E_DIR,
testMatch,
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: 0,
workers: workers && Number.isFinite(workers) && workers > 0 ? workers : undefined,
timeout: Number.isFinite(timeout) && timeout > 0 ? timeout : 180000,
grep: grepSource ? new RegExp(grepSource) : undefined,
grepInvert: grepInvertSource ? new RegExp(grepInvertSource) : undefined,
reporter: [
['html', { outputFolder: path.join(reportDir, 'html'), open: 'never' }],
['json', { outputFile: path.join(reportDir, 'results.json') }],
['list'],
],
snapshotPathTemplate: `${E2E_DIR}/{testFileDir}/snapshot/{arg}{ext}`,
use: {
trace: 'retain-on-failure',
video: 'retain-on-failure',
headless: !headed,
ignoreHTTPSErrors: process.env.BAI_SMOKE_INSECURE_TLS === '1',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], locale: 'en-US' },
},
],
});
Loading
Loading