game-harness 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +140 -0
- package/CHANGELOG.md +69 -0
- package/LICENSE +21 -0
- package/README.md +568 -0
- package/bin/test-harness-visual-battery.mjs +6 -0
- package/dist/cjs/bin/visual-battery.js +43 -0
- package/dist/cjs/browser-config.js +83 -0
- package/dist/cjs/chromium-launch.js +37 -0
- package/dist/cjs/index.js +10 -0
- package/dist/cjs/lighthouse.js +73 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/playwright-config.js +303 -0
- package/dist/cjs/production-runtime.js +315 -0
- package/dist/cjs/release-ladder.js +40 -0
- package/dist/cjs/silent-qa.js +75 -0
- package/dist/cjs/visual-battery.js +247 -0
- package/dist/esm/bin/visual-battery.js +41 -0
- package/dist/esm/browser-config.js +80 -0
- package/dist/esm/chromium-launch.js +34 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/lighthouse.js +70 -0
- package/dist/esm/playwright-config.js +292 -0
- package/dist/esm/production-runtime.js +304 -0
- package/dist/esm/release-ladder.js +37 -0
- package/dist/esm/silent-qa.js +67 -0
- package/dist/esm/visual-battery.js +239 -0
- package/dist/types/bin/visual-battery.d.cts +2 -0
- package/dist/types/bin/visual-battery.d.ts +2 -0
- package/dist/types/browser-config.d.cts +100 -0
- package/dist/types/browser-config.d.ts +100 -0
- package/dist/types/chromium-launch.d.cts +27 -0
- package/dist/types/chromium-launch.d.ts +27 -0
- package/dist/types/index.d.cts +3 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/lighthouse.d.cts +44 -0
- package/dist/types/lighthouse.d.ts +44 -0
- package/dist/types/playwright-config.d.cts +122 -0
- package/dist/types/playwright-config.d.ts +122 -0
- package/dist/types/production-runtime.d.cts +105 -0
- package/dist/types/production-runtime.d.ts +105 -0
- package/dist/types/release-ladder.d.cts +38 -0
- package/dist/types/release-ladder.d.ts +38 -0
- package/dist/types/silent-qa.d.cts +47 -0
- package/dist/types/silent-qa.d.ts +47 -0
- package/dist/types/visual-battery.d.cts +69 -0
- package/dist/types/visual-battery.d.ts +69 -0
- package/docs/404.md +17 -0
- package/docs/agent-guide.md +10 -0
- package/docs/architecture.md +81 -0
- package/docs/assets/game-harness-hero.webp +0 -0
- package/docs/changelog.md +9 -0
- package/docs/contributing.md +21 -0
- package/docs/entry-points.md +41 -0
- package/docs/getting-started.md +42 -0
- package/docs/guides/chromium-and-silent-qa.md +72 -0
- package/docs/guides/lighthouse-and-release-ladder.md +62 -0
- package/docs/guides/playwright.md +58 -0
- package/docs/guides/production-runtime.md +61 -0
- package/docs/guides/visual-battery.md +58 -0
- package/docs/guides/vitest.md +41 -0
- package/docs/introduction.md +29 -0
- package/docs/package.json +13 -0
- package/docs/quick-start.md +47 -0
- package/docs/reference/troubleshooting.md +33 -0
- package/docs/security.md +15 -0
- package/docs/sourcey.config.ts +79 -0
- package/llms.txt +32 -0
- package/maestro/smoke.template.yaml +11 -0
- package/package.json +196 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type Browser, chromium, type Page } from '@playwright/test';
|
|
2
|
+
import { type ChromiumGpuMode } from './chromium-launch.js';
|
|
3
|
+
import { type OpenSilentGameOptions, type SilentQueryValue } from './playwright-config.js';
|
|
4
|
+
type BrowserLaunchOptions = NonNullable<Parameters<typeof chromium.launch>[0]>;
|
|
5
|
+
type BrowserPageOptions = Parameters<Browser['newPage']>[0];
|
|
6
|
+
export interface ProductionRuntimeServerOptions {
|
|
7
|
+
/** Executable to launch directly. Shell command strings are intentionally unsupported. */
|
|
8
|
+
command: string;
|
|
9
|
+
/** Arguments passed directly to `command`. Use the server's strict-port option. */
|
|
10
|
+
args?: readonly string[];
|
|
11
|
+
/** Child working directory. Defaults to the current process directory. */
|
|
12
|
+
cwd?: string;
|
|
13
|
+
/** Environment additions or overrides for the child process. */
|
|
14
|
+
env?: Readonly<NodeJS.ProcessEnv>;
|
|
15
|
+
/** URL polled for readiness. Defaults to the runtime `url`. */
|
|
16
|
+
readyUrl?: string;
|
|
17
|
+
/** Maximum startup time. Defaults to 15 seconds. */
|
|
18
|
+
startupTimeoutMs?: number;
|
|
19
|
+
/** Graceful shutdown time before SIGKILL. Defaults to 5 seconds. */
|
|
20
|
+
shutdownTimeoutMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ProductionRuntimeOptions {
|
|
23
|
+
/** Production artifact or exact-live URL to verify. */
|
|
24
|
+
url: string;
|
|
25
|
+
/** Optional owned preview server. Existing processes are never reused. */
|
|
26
|
+
server?: ProductionRuntimeServerOptions;
|
|
27
|
+
/** Chromium options. `--mute-audio` is always de-duplicated and applied last. */
|
|
28
|
+
browserLaunchOptions?: BrowserLaunchOptions;
|
|
29
|
+
/** Renderer profile. Defaults to native Chromium selection (`auto`). */
|
|
30
|
+
gpuMode?: ChromiumGpuMode;
|
|
31
|
+
/** Options for the fresh browser page. */
|
|
32
|
+
pageOptions?: BrowserPageOptions;
|
|
33
|
+
/** Extra query parameters composed with the mandatory runtime mute. */
|
|
34
|
+
silentParameters?: Readonly<Record<string, SilentQueryValue>>;
|
|
35
|
+
/** Silent marker/query overrides. The marker timeout defaults to 15 seconds here. */
|
|
36
|
+
silentOptions?: OpenSilentGameOptions;
|
|
37
|
+
/** Values written before application code and required to remain byte-for-byte unchanged. */
|
|
38
|
+
localStorageSentinels?: Readonly<Record<string, string | null>>;
|
|
39
|
+
/** Required game-specific identity plus primary UI/canvas assertions. */
|
|
40
|
+
assertReady: (page: Page) => Promise<void>;
|
|
41
|
+
/** Optional engine-specific assertion such as `window.Howler._muted === true`. */
|
|
42
|
+
assertSilentState?: (page: Page) => Promise<void>;
|
|
43
|
+
/** Brief post-ready observation window for deferred runtime errors. Defaults to 250 ms. */
|
|
44
|
+
settleTimeMs?: number;
|
|
45
|
+
}
|
|
46
|
+
/** Evidence returned by a successful `verifyProductionRuntime()` call. */
|
|
47
|
+
export interface ProductionRuntimeResult {
|
|
48
|
+
/** The page's URL after navigation, including the applied silent-QA query parameters. */
|
|
49
|
+
finalUrl: string;
|
|
50
|
+
/** Every requested `localStorageSentinels` key, read back after the boot completed. */
|
|
51
|
+
localStorage: Record<string, string | null>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* One runtime failure captured while the game booted: an uncaught page
|
|
55
|
+
* error, a `console.error` call, a network request that failed outright, or
|
|
56
|
+
* an HTTP response with a 4xx/5xx status. `verifyProductionRuntime()`
|
|
57
|
+
* accumulates these and fails closed if any were recorded.
|
|
58
|
+
*/
|
|
59
|
+
export interface ProductionRuntimeIssue {
|
|
60
|
+
kind: 'console' | 'http' | 'pageerror' | 'requestfailed';
|
|
61
|
+
message: string;
|
|
62
|
+
/** The request/response URL, present for `'http'` and `'requestfailed'` issues. */
|
|
63
|
+
url?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface AvailableProductionPortOptions {
|
|
66
|
+
/** Loopback interface used by the owned preview server. Defaults to IPv4 localhost. */
|
|
67
|
+
host?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Thrown by every failure path in this module — an unreachable/occupied
|
|
71
|
+
* readiness URL, a server that never became ready, a masked or
|
|
72
|
+
* software-rendered WebGL context, a changed localStorage sentinel, or one
|
|
73
|
+
* or more recorded {@link ProductionRuntimeIssue}s. Callers can inspect
|
|
74
|
+
* `.issues` for the underlying runtime errors instead of parsing `.message`.
|
|
75
|
+
*/
|
|
76
|
+
export declare class ProductionRuntimeVerificationError extends Error {
|
|
77
|
+
/** Runtime issues recorded before this error was thrown, if any. Empty for pure validation failures. */
|
|
78
|
+
readonly issues: readonly ProductionRuntimeIssue[];
|
|
79
|
+
constructor(message: string, issues?: readonly ProductionRuntimeIssue[], cause?: unknown);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Finds and process-reserves an available loopback port for an owned production
|
|
83
|
+
* preview. The reservation prevents parallel verifier setup in this process
|
|
84
|
+
* from selecting the same port; the preview must still bind with strict-port
|
|
85
|
+
* semantics so an external race fails closed.
|
|
86
|
+
*/
|
|
87
|
+
export declare function findAvailableProductionPort(options?: AvailableProductionPortOptions): Promise<number>;
|
|
88
|
+
export interface WebGLRendererInfo {
|
|
89
|
+
/** `UNMASKED_RENDERER_WEBGL` when available, otherwise the generic (often masked) `RENDERER` parameter. */
|
|
90
|
+
renderer: string;
|
|
91
|
+
/** `UNMASKED_VENDOR_WEBGL` when available, otherwise the generic (often masked) `VENDOR` parameter. */
|
|
92
|
+
vendor: string;
|
|
93
|
+
/** True only when Chromium exposed `WEBGL_debug_renderer_info`. */
|
|
94
|
+
unmasked: boolean;
|
|
95
|
+
}
|
|
96
|
+
/** Reads the unmasked WebGL renderer already attached to the selected game canvas. */
|
|
97
|
+
export declare function readWebGLRenderer(page: Page, canvasSelector?: string): Promise<WebGLRendererInfo>;
|
|
98
|
+
/** Requires a real hardware-backed WebGL renderer and returns its identity. */
|
|
99
|
+
export declare function requireHardwareWebGL(page: Page, canvasSelector?: string): Promise<WebGLRendererInfo>;
|
|
100
|
+
/**
|
|
101
|
+
* Boots a built or deployed game in a fresh, fail-closed, silent Chromium and
|
|
102
|
+
* requires game-specific identity/UI proof before accepting the runtime.
|
|
103
|
+
*/
|
|
104
|
+
export declare function verifyProductionRuntime(options: ProductionRuntimeOptions): Promise<ProductionRuntimeResult>;
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type Browser, chromium, type Page } from '@playwright/test';
|
|
2
|
+
import { type ChromiumGpuMode } from './chromium-launch.js';
|
|
3
|
+
import { type OpenSilentGameOptions, type SilentQueryValue } from './playwright-config.js';
|
|
4
|
+
type BrowserLaunchOptions = NonNullable<Parameters<typeof chromium.launch>[0]>;
|
|
5
|
+
type BrowserPageOptions = Parameters<Browser['newPage']>[0];
|
|
6
|
+
export interface ProductionRuntimeServerOptions {
|
|
7
|
+
/** Executable to launch directly. Shell command strings are intentionally unsupported. */
|
|
8
|
+
command: string;
|
|
9
|
+
/** Arguments passed directly to `command`. Use the server's strict-port option. */
|
|
10
|
+
args?: readonly string[];
|
|
11
|
+
/** Child working directory. Defaults to the current process directory. */
|
|
12
|
+
cwd?: string;
|
|
13
|
+
/** Environment additions or overrides for the child process. */
|
|
14
|
+
env?: Readonly<NodeJS.ProcessEnv>;
|
|
15
|
+
/** URL polled for readiness. Defaults to the runtime `url`. */
|
|
16
|
+
readyUrl?: string;
|
|
17
|
+
/** Maximum startup time. Defaults to 15 seconds. */
|
|
18
|
+
startupTimeoutMs?: number;
|
|
19
|
+
/** Graceful shutdown time before SIGKILL. Defaults to 5 seconds. */
|
|
20
|
+
shutdownTimeoutMs?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ProductionRuntimeOptions {
|
|
23
|
+
/** Production artifact or exact-live URL to verify. */
|
|
24
|
+
url: string;
|
|
25
|
+
/** Optional owned preview server. Existing processes are never reused. */
|
|
26
|
+
server?: ProductionRuntimeServerOptions;
|
|
27
|
+
/** Chromium options. `--mute-audio` is always de-duplicated and applied last. */
|
|
28
|
+
browserLaunchOptions?: BrowserLaunchOptions;
|
|
29
|
+
/** Renderer profile. Defaults to native Chromium selection (`auto`). */
|
|
30
|
+
gpuMode?: ChromiumGpuMode;
|
|
31
|
+
/** Options for the fresh browser page. */
|
|
32
|
+
pageOptions?: BrowserPageOptions;
|
|
33
|
+
/** Extra query parameters composed with the mandatory runtime mute. */
|
|
34
|
+
silentParameters?: Readonly<Record<string, SilentQueryValue>>;
|
|
35
|
+
/** Silent marker/query overrides. The marker timeout defaults to 15 seconds here. */
|
|
36
|
+
silentOptions?: OpenSilentGameOptions;
|
|
37
|
+
/** Values written before application code and required to remain byte-for-byte unchanged. */
|
|
38
|
+
localStorageSentinels?: Readonly<Record<string, string | null>>;
|
|
39
|
+
/** Required game-specific identity plus primary UI/canvas assertions. */
|
|
40
|
+
assertReady: (page: Page) => Promise<void>;
|
|
41
|
+
/** Optional engine-specific assertion such as `window.Howler._muted === true`. */
|
|
42
|
+
assertSilentState?: (page: Page) => Promise<void>;
|
|
43
|
+
/** Brief post-ready observation window for deferred runtime errors. Defaults to 250 ms. */
|
|
44
|
+
settleTimeMs?: number;
|
|
45
|
+
}
|
|
46
|
+
/** Evidence returned by a successful `verifyProductionRuntime()` call. */
|
|
47
|
+
export interface ProductionRuntimeResult {
|
|
48
|
+
/** The page's URL after navigation, including the applied silent-QA query parameters. */
|
|
49
|
+
finalUrl: string;
|
|
50
|
+
/** Every requested `localStorageSentinels` key, read back after the boot completed. */
|
|
51
|
+
localStorage: Record<string, string | null>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* One runtime failure captured while the game booted: an uncaught page
|
|
55
|
+
* error, a `console.error` call, a network request that failed outright, or
|
|
56
|
+
* an HTTP response with a 4xx/5xx status. `verifyProductionRuntime()`
|
|
57
|
+
* accumulates these and fails closed if any were recorded.
|
|
58
|
+
*/
|
|
59
|
+
export interface ProductionRuntimeIssue {
|
|
60
|
+
kind: 'console' | 'http' | 'pageerror' | 'requestfailed';
|
|
61
|
+
message: string;
|
|
62
|
+
/** The request/response URL, present for `'http'` and `'requestfailed'` issues. */
|
|
63
|
+
url?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface AvailableProductionPortOptions {
|
|
66
|
+
/** Loopback interface used by the owned preview server. Defaults to IPv4 localhost. */
|
|
67
|
+
host?: string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Thrown by every failure path in this module — an unreachable/occupied
|
|
71
|
+
* readiness URL, a server that never became ready, a masked or
|
|
72
|
+
* software-rendered WebGL context, a changed localStorage sentinel, or one
|
|
73
|
+
* or more recorded {@link ProductionRuntimeIssue}s. Callers can inspect
|
|
74
|
+
* `.issues` for the underlying runtime errors instead of parsing `.message`.
|
|
75
|
+
*/
|
|
76
|
+
export declare class ProductionRuntimeVerificationError extends Error {
|
|
77
|
+
/** Runtime issues recorded before this error was thrown, if any. Empty for pure validation failures. */
|
|
78
|
+
readonly issues: readonly ProductionRuntimeIssue[];
|
|
79
|
+
constructor(message: string, issues?: readonly ProductionRuntimeIssue[], cause?: unknown);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Finds and process-reserves an available loopback port for an owned production
|
|
83
|
+
* preview. The reservation prevents parallel verifier setup in this process
|
|
84
|
+
* from selecting the same port; the preview must still bind with strict-port
|
|
85
|
+
* semantics so an external race fails closed.
|
|
86
|
+
*/
|
|
87
|
+
export declare function findAvailableProductionPort(options?: AvailableProductionPortOptions): Promise<number>;
|
|
88
|
+
export interface WebGLRendererInfo {
|
|
89
|
+
/** `UNMASKED_RENDERER_WEBGL` when available, otherwise the generic (often masked) `RENDERER` parameter. */
|
|
90
|
+
renderer: string;
|
|
91
|
+
/** `UNMASKED_VENDOR_WEBGL` when available, otherwise the generic (often masked) `VENDOR` parameter. */
|
|
92
|
+
vendor: string;
|
|
93
|
+
/** True only when Chromium exposed `WEBGL_debug_renderer_info`. */
|
|
94
|
+
unmasked: boolean;
|
|
95
|
+
}
|
|
96
|
+
/** Reads the unmasked WebGL renderer already attached to the selected game canvas. */
|
|
97
|
+
export declare function readWebGLRenderer(page: Page, canvasSelector?: string): Promise<WebGLRendererInfo>;
|
|
98
|
+
/** Requires a real hardware-backed WebGL renderer and returns its identity. */
|
|
99
|
+
export declare function requireHardwareWebGL(page: Page, canvasSelector?: string): Promise<WebGLRendererInfo>;
|
|
100
|
+
/**
|
|
101
|
+
* Boots a built or deployed game in a fresh, fail-closed, silent Chromium and
|
|
102
|
+
* requires game-specific identity/UI proof before accepting the runtime.
|
|
103
|
+
*/
|
|
104
|
+
export declare function verifyProductionRuntime(options: ProductionRuntimeOptions): Promise<ProductionRuntimeResult>;
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface ReleaseLadderStep {
|
|
2
|
+
/** Label logged before/after the step runs and used to identify it in a failure result. */
|
|
3
|
+
name: string;
|
|
4
|
+
/** The step's work. A thrown error or rejected promise stops the ladder at this step. */
|
|
5
|
+
run: () => void | Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export interface ReleaseLadderResult {
|
|
8
|
+
/** True only if every step completed without throwing. */
|
|
9
|
+
ok: boolean;
|
|
10
|
+
/** Names of steps that completed successfully, in order, up to (and excluding) any failure. */
|
|
11
|
+
ranSteps: string[];
|
|
12
|
+
/** Name of the step that threw, present only when `ok` is false. */
|
|
13
|
+
failedStep?: string;
|
|
14
|
+
/** The value thrown by `failedStep`'s `run()`, present only when `ok` is false. */
|
|
15
|
+
error?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface VerifyReleaseLadderOptions {
|
|
18
|
+
/** Called before and after each step. Defaults to `console.log` prefixed with `[verify]`. */
|
|
19
|
+
log?: (msg: string) => void;
|
|
20
|
+
/** Called on step failure, once for the failure message and again with the error's message if it's an `Error`. Defaults to `console.error` prefixed with `[verify]`. */
|
|
21
|
+
error?: (msg: string) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Thin orchestrator for a `verify:*` release ladder — an ordered list of
|
|
25
|
+
* named steps (lint → typecheck → test → build → browser verifies →
|
|
26
|
+
* screenshots → native sync, or whatever a given repo's ladder is), run in
|
|
27
|
+
* sequence and stopped at the first failure with a labeled summary.
|
|
28
|
+
*
|
|
29
|
+
* Generalizes the reach-for-the-sky pattern of ~17 discrete
|
|
30
|
+
* `node scripts/verify-X.mjs` files composed via a shell `&&` chain into a
|
|
31
|
+
* single reusable primitive: each step is a plain function (sync or async),
|
|
32
|
+
* so a repo can inline its logic or delegate to existing scripts via
|
|
33
|
+
* `execSync`.
|
|
34
|
+
*
|
|
35
|
+
* Never throws — returns a result object so callers can decide how to
|
|
36
|
+
* report/exit. The CLI convention is `process.exit(result.ok ? 0 : 1)`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifyReleaseLadder(steps: ReleaseLadderStep[], options?: VerifyReleaseLadderOptions): Promise<ReleaseLadderResult>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface ReleaseLadderStep {
|
|
2
|
+
/** Label logged before/after the step runs and used to identify it in a failure result. */
|
|
3
|
+
name: string;
|
|
4
|
+
/** The step's work. A thrown error or rejected promise stops the ladder at this step. */
|
|
5
|
+
run: () => void | Promise<void>;
|
|
6
|
+
}
|
|
7
|
+
export interface ReleaseLadderResult {
|
|
8
|
+
/** True only if every step completed without throwing. */
|
|
9
|
+
ok: boolean;
|
|
10
|
+
/** Names of steps that completed successfully, in order, up to (and excluding) any failure. */
|
|
11
|
+
ranSteps: string[];
|
|
12
|
+
/** Name of the step that threw, present only when `ok` is false. */
|
|
13
|
+
failedStep?: string;
|
|
14
|
+
/** The value thrown by `failedStep`'s `run()`, present only when `ok` is false. */
|
|
15
|
+
error?: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface VerifyReleaseLadderOptions {
|
|
18
|
+
/** Called before and after each step. Defaults to `console.log` prefixed with `[verify]`. */
|
|
19
|
+
log?: (msg: string) => void;
|
|
20
|
+
/** Called on step failure, once for the failure message and again with the error's message if it's an `Error`. Defaults to `console.error` prefixed with `[verify]`. */
|
|
21
|
+
error?: (msg: string) => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Thin orchestrator for a `verify:*` release ladder — an ordered list of
|
|
25
|
+
* named steps (lint → typecheck → test → build → browser verifies →
|
|
26
|
+
* screenshots → native sync, or whatever a given repo's ladder is), run in
|
|
27
|
+
* sequence and stopped at the first failure with a labeled summary.
|
|
28
|
+
*
|
|
29
|
+
* Generalizes the reach-for-the-sky pattern of ~17 discrete
|
|
30
|
+
* `node scripts/verify-X.mjs` files composed via a shell `&&` chain into a
|
|
31
|
+
* single reusable primitive: each step is a plain function (sync or async),
|
|
32
|
+
* so a repo can inline its logic or delegate to existing scripts via
|
|
33
|
+
* `execSync`.
|
|
34
|
+
*
|
|
35
|
+
* Never throws — returns a result object so callers can decide how to
|
|
36
|
+
* report/exit. The CLI convention is `process.exit(result.ok ? 0 : 1)`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function verifyReleaseLadder(steps: ReleaseLadderStep[], options?: VerifyReleaseLadderOptions): Promise<ReleaseLadderResult>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const SILENT_QA_QUERY_PARAMETER = "muted";
|
|
2
|
+
export declare const SILENT_QA_QUERY_VALUE = "1";
|
|
3
|
+
export declare const SILENT_QA_MARKER_ATTRIBUTE = "data-audio-mode";
|
|
4
|
+
export declare const SILENT_QA_MARKER_VALUE = "muted-test";
|
|
5
|
+
export interface SilentQaMarkerTarget {
|
|
6
|
+
setAttribute(name: string, value: string): void;
|
|
7
|
+
}
|
|
8
|
+
export interface ActivateSilentQaOptions {
|
|
9
|
+
/** Query string to inspect. Defaults to `window.location.search` in a browser. */
|
|
10
|
+
search?: string;
|
|
11
|
+
/** Runtime-only mute parameter. Defaults to `muted`. */
|
|
12
|
+
queryParameter?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Element that owns the mute-ready marker. Defaults to `document.documentElement`.
|
|
15
|
+
* Pass `null` for a non-DOM runtime.
|
|
16
|
+
*/
|
|
17
|
+
markerTarget?: SilentQaMarkerTarget | null;
|
|
18
|
+
/** Marker attribute set only after the audio engine's mute callback succeeds. */
|
|
19
|
+
markerAttribute?: string;
|
|
20
|
+
/** Marker value set only after the audio engine's mute callback succeeds. */
|
|
21
|
+
markerValue?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Detects a page-lifetime mute request by parameter presence.
|
|
25
|
+
*
|
|
26
|
+
* The value is intentionally ignored so stale links such as `?muted=0` cannot
|
|
27
|
+
* make an agent-controlled session audible.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isSilentQaRequested(search?: string, queryParameter?: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Activates runtime-only silent QA through a consumer-owned audio mute callback.
|
|
32
|
+
*
|
|
33
|
+
* The package stays audio-engine agnostic: consumers may mute Howler, WebAudio,
|
|
34
|
+
* HTMLMediaElement, or another engine. The DOM marker is published only after
|
|
35
|
+
* the callback returns successfully, allowing browser tests to fail closed.
|
|
36
|
+
* This helper never reads or writes a player's persisted audio preference.
|
|
37
|
+
*/
|
|
38
|
+
export declare function activateSilentQa(mute: () => void, options?: ActivateSilentQaOptions): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Asynchronous counterpart to {@link activateSilentQa}. The readiness marker
|
|
41
|
+
* is not published until the consumer's mute promise fulfills.
|
|
42
|
+
*/
|
|
43
|
+
export declare function activateSilentQaAsync(mute: () => void | PromiseLike<void>, options?: ActivateSilentQaOptions): Promise<boolean>;
|
|
44
|
+
/** True after this module has successfully activated the page-lifetime override. */
|
|
45
|
+
export declare function isSilentQaActive(): boolean;
|
|
46
|
+
/** Test hook; production code must never disable a page-lifetime override. */
|
|
47
|
+
export declare function _resetSilentQaForTests(): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const SILENT_QA_QUERY_PARAMETER = "muted";
|
|
2
|
+
export declare const SILENT_QA_QUERY_VALUE = "1";
|
|
3
|
+
export declare const SILENT_QA_MARKER_ATTRIBUTE = "data-audio-mode";
|
|
4
|
+
export declare const SILENT_QA_MARKER_VALUE = "muted-test";
|
|
5
|
+
export interface SilentQaMarkerTarget {
|
|
6
|
+
setAttribute(name: string, value: string): void;
|
|
7
|
+
}
|
|
8
|
+
export interface ActivateSilentQaOptions {
|
|
9
|
+
/** Query string to inspect. Defaults to `window.location.search` in a browser. */
|
|
10
|
+
search?: string;
|
|
11
|
+
/** Runtime-only mute parameter. Defaults to `muted`. */
|
|
12
|
+
queryParameter?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Element that owns the mute-ready marker. Defaults to `document.documentElement`.
|
|
15
|
+
* Pass `null` for a non-DOM runtime.
|
|
16
|
+
*/
|
|
17
|
+
markerTarget?: SilentQaMarkerTarget | null;
|
|
18
|
+
/** Marker attribute set only after the audio engine's mute callback succeeds. */
|
|
19
|
+
markerAttribute?: string;
|
|
20
|
+
/** Marker value set only after the audio engine's mute callback succeeds. */
|
|
21
|
+
markerValue?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Detects a page-lifetime mute request by parameter presence.
|
|
25
|
+
*
|
|
26
|
+
* The value is intentionally ignored so stale links such as `?muted=0` cannot
|
|
27
|
+
* make an agent-controlled session audible.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isSilentQaRequested(search?: string, queryParameter?: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Activates runtime-only silent QA through a consumer-owned audio mute callback.
|
|
32
|
+
*
|
|
33
|
+
* The package stays audio-engine agnostic: consumers may mute Howler, WebAudio,
|
|
34
|
+
* HTMLMediaElement, or another engine. The DOM marker is published only after
|
|
35
|
+
* the callback returns successfully, allowing browser tests to fail closed.
|
|
36
|
+
* This helper never reads or writes a player's persisted audio preference.
|
|
37
|
+
*/
|
|
38
|
+
export declare function activateSilentQa(mute: () => void, options?: ActivateSilentQaOptions): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Asynchronous counterpart to {@link activateSilentQa}. The readiness marker
|
|
41
|
+
* is not published until the consumer's mute promise fulfills.
|
|
42
|
+
*/
|
|
43
|
+
export declare function activateSilentQaAsync(mute: () => void | PromiseLike<void>, options?: ActivateSilentQaOptions): Promise<boolean>;
|
|
44
|
+
/** True after this module has successfully activated the page-lifetime override. */
|
|
45
|
+
export declare function isSilentQaActive(): boolean;
|
|
46
|
+
/** Test hook; production code must never disable a page-lifetime override. */
|
|
47
|
+
export declare function _resetSilentQaForTests(): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface VisualBatteryCommand {
|
|
2
|
+
/** Executable invoked directly, without a shell. */
|
|
3
|
+
command: string;
|
|
4
|
+
/** Fixed arguments inserted before the discovered harness paths. */
|
|
5
|
+
args?: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
export interface VisualBatteryOptions {
|
|
8
|
+
/** CI mode: refuses to run with a dirty baseline dir, fails on drift instead of updating. */
|
|
9
|
+
ci?: boolean;
|
|
10
|
+
/** Repo root the git commands run relative to. Defaults to `process.cwd()`. */
|
|
11
|
+
cwd?: string;
|
|
12
|
+
/**
|
|
13
|
+
* The `pnpm test:browser`-equivalent command, without harness paths.
|
|
14
|
+
* Strings are split on whitespace for backward compatibility; use the
|
|
15
|
+
* object form when an argument contains spaces. Commands execute directly,
|
|
16
|
+
* never through a shell. Defaults to `'pnpm test:browser'`.
|
|
17
|
+
*/
|
|
18
|
+
testCommand?: string | VisualBatteryCommand;
|
|
19
|
+
/** Baseline root relative to `cwd`. Defaults to `${harnessGlob}/__screenshots__`; `baselineProfile` is appended below it. */
|
|
20
|
+
baselinesDir?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional platform/profile directory below the baseline root (for example
|
|
23
|
+
* `linux`). The profile is also exposed to Vite browser tests as
|
|
24
|
+
* `VITE_VISUAL_BASELINE_PROFILE`, allowing byte-exact baselines to remain
|
|
25
|
+
* strict on renderers that cannot produce identical PNGs.
|
|
26
|
+
*/
|
|
27
|
+
baselineProfile?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Harness basenames that each need a fresh browser process. Use this for
|
|
30
|
+
* WebGL screenshots whose renderer state can drift after earlier canvases
|
|
31
|
+
* have shared a long-lived Chromium process.
|
|
32
|
+
*/
|
|
33
|
+
isolatedHarnessFiles?: string[];
|
|
34
|
+
/** Logger, swappable for tests. Defaults to `console.log`/`console.error`. */
|
|
35
|
+
log?: (msg: string) => void;
|
|
36
|
+
error?: (msg: string) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Thrown by every `runVisualBattery()` failure path — a missing harness dir,
|
|
40
|
+
* no discovered `.browser.test.ts(x)` files, a misplaced `__screenshots__`
|
|
41
|
+
* directory, a dirty baseline dir in `--ci` mode, a failing harness run, or
|
|
42
|
+
* detected drift while `ci: true`. Callers (tests, other tooling) can catch
|
|
43
|
+
* this specific type instead of `process.exit`, which only the CLI entry
|
|
44
|
+
* point (`bin/test-harness-visual-battery`) calls.
|
|
45
|
+
*/
|
|
46
|
+
export declare class VisualBatteryError extends Error {
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Deterministic git-diff-based visual-regression gate.
|
|
50
|
+
*
|
|
51
|
+
* Runs every `.browser.test.tsx` harness file under `harnessDir`, then
|
|
52
|
+
* diffs the resulting `__screenshots__/*.png` baselines against what's
|
|
53
|
+
* committed in git. No pixel-threshold fuzzing, no flaky perceptual
|
|
54
|
+
* comparison — a screenshot either byte-matches the committed baseline
|
|
55
|
+
* (via `git status --porcelain`) or it doesn't.
|
|
56
|
+
*
|
|
57
|
+
* - Update mode (`ci: false`, the default): runs the harnesses, lets new
|
|
58
|
+
* baselines land on disk, reports what changed so a human can review
|
|
59
|
+
* `git diff` and commit intentionally.
|
|
60
|
+
* - CI mode (`ci: true`): refuses to run at all if the baselines dir has
|
|
61
|
+
* uncommitted changes already (an untrusted starting state), then fails
|
|
62
|
+
* the process if the run produces any drift from the committed baseline.
|
|
63
|
+
*
|
|
64
|
+
* Throws `VisualBatteryError` on any failure condition instead of calling
|
|
65
|
+
* `process.exit` directly, so callers (tests, other tooling) can catch it;
|
|
66
|
+
* the CLI entry point (`bin/test-harness-visual-battery`) is the thing that
|
|
67
|
+
* exits the process.
|
|
68
|
+
*/
|
|
69
|
+
export declare function runVisualBattery(harnessDir: string, options?: VisualBatteryOptions): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export interface VisualBatteryCommand {
|
|
2
|
+
/** Executable invoked directly, without a shell. */
|
|
3
|
+
command: string;
|
|
4
|
+
/** Fixed arguments inserted before the discovered harness paths. */
|
|
5
|
+
args?: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
export interface VisualBatteryOptions {
|
|
8
|
+
/** CI mode: refuses to run with a dirty baseline dir, fails on drift instead of updating. */
|
|
9
|
+
ci?: boolean;
|
|
10
|
+
/** Repo root the git commands run relative to. Defaults to `process.cwd()`. */
|
|
11
|
+
cwd?: string;
|
|
12
|
+
/**
|
|
13
|
+
* The `pnpm test:browser`-equivalent command, without harness paths.
|
|
14
|
+
* Strings are split on whitespace for backward compatibility; use the
|
|
15
|
+
* object form when an argument contains spaces. Commands execute directly,
|
|
16
|
+
* never through a shell. Defaults to `'pnpm test:browser'`.
|
|
17
|
+
*/
|
|
18
|
+
testCommand?: string | VisualBatteryCommand;
|
|
19
|
+
/** Baseline root relative to `cwd`. Defaults to `${harnessGlob}/__screenshots__`; `baselineProfile` is appended below it. */
|
|
20
|
+
baselinesDir?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Optional platform/profile directory below the baseline root (for example
|
|
23
|
+
* `linux`). The profile is also exposed to Vite browser tests as
|
|
24
|
+
* `VITE_VISUAL_BASELINE_PROFILE`, allowing byte-exact baselines to remain
|
|
25
|
+
* strict on renderers that cannot produce identical PNGs.
|
|
26
|
+
*/
|
|
27
|
+
baselineProfile?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Harness basenames that each need a fresh browser process. Use this for
|
|
30
|
+
* WebGL screenshots whose renderer state can drift after earlier canvases
|
|
31
|
+
* have shared a long-lived Chromium process.
|
|
32
|
+
*/
|
|
33
|
+
isolatedHarnessFiles?: string[];
|
|
34
|
+
/** Logger, swappable for tests. Defaults to `console.log`/`console.error`. */
|
|
35
|
+
log?: (msg: string) => void;
|
|
36
|
+
error?: (msg: string) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Thrown by every `runVisualBattery()` failure path — a missing harness dir,
|
|
40
|
+
* no discovered `.browser.test.ts(x)` files, a misplaced `__screenshots__`
|
|
41
|
+
* directory, a dirty baseline dir in `--ci` mode, a failing harness run, or
|
|
42
|
+
* detected drift while `ci: true`. Callers (tests, other tooling) can catch
|
|
43
|
+
* this specific type instead of `process.exit`, which only the CLI entry
|
|
44
|
+
* point (`bin/test-harness-visual-battery`) calls.
|
|
45
|
+
*/
|
|
46
|
+
export declare class VisualBatteryError extends Error {
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Deterministic git-diff-based visual-regression gate.
|
|
50
|
+
*
|
|
51
|
+
* Runs every `.browser.test.tsx` harness file under `harnessDir`, then
|
|
52
|
+
* diffs the resulting `__screenshots__/*.png` baselines against what's
|
|
53
|
+
* committed in git. No pixel-threshold fuzzing, no flaky perceptual
|
|
54
|
+
* comparison — a screenshot either byte-matches the committed baseline
|
|
55
|
+
* (via `git status --porcelain`) or it doesn't.
|
|
56
|
+
*
|
|
57
|
+
* - Update mode (`ci: false`, the default): runs the harnesses, lets new
|
|
58
|
+
* baselines land on disk, reports what changed so a human can review
|
|
59
|
+
* `git diff` and commit intentionally.
|
|
60
|
+
* - CI mode (`ci: true`): refuses to run at all if the baselines dir has
|
|
61
|
+
* uncommitted changes already (an untrusted starting state), then fails
|
|
62
|
+
* the process if the run produces any drift from the committed baseline.
|
|
63
|
+
*
|
|
64
|
+
* Throws `VisualBatteryError` on any failure condition instead of calling
|
|
65
|
+
* `process.exit` directly, so callers (tests, other tooling) can catch it;
|
|
66
|
+
* the CLI entry point (`bin/test-harness-visual-battery`) is the thing that
|
|
67
|
+
* exits the process.
|
|
68
|
+
*/
|
|
69
|
+
export declare function runVisualBattery(harnessDir: string, options?: VisualBatteryOptions): void;
|
package/docs/404.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Page not found
|
|
3
|
+
description: This page does not exist.
|
|
4
|
+
editUrl: false
|
|
5
|
+
head:
|
|
6
|
+
- tag: meta
|
|
7
|
+
attrs:
|
|
8
|
+
property: og:title
|
|
9
|
+
content: Page not found
|
|
10
|
+
tableOfContents: false
|
|
11
|
+
sidebar:
|
|
12
|
+
hidden: true
|
|
13
|
+
template: splash
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
The page you're looking for doesn't exist. Head back to the
|
|
17
|
+
[docs home](/game-harness/) or the [getting started guide](/game-harness/getting-started/).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Agent development guide
|
|
3
|
+
description: The safe, policy-compliant workflow for trusted coding agents.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Repository instructions for agents live in [AGENTS.md](https://github.com/jbcom/game-harness/blob/main/AGENTS.md). They cover the public API, silent-browser contract, package boundaries, validation, Sourcey documentation architecture, Conventional Commits, and protected merge workflow.
|
|
7
|
+
|
|
8
|
+
The repository-root `llms.txt` is a concise agent index. Sourcey generates
|
|
9
|
+
separate `llms.txt` and `llms-full.txt` files in the documentation build; those
|
|
10
|
+
describe this documentation site and are not manually maintained copies.
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
Game Harness is a small collection of composable verification primitives. It
|
|
4
|
+
does not own a game's tests or runtime state; it makes the boundaries around
|
|
5
|
+
those tests explicit and fail-closed.
|
|
6
|
+
|
|
7
|
+
## Package boundaries
|
|
8
|
+
|
|
9
|
+
| Entry point | Responsibility | Required peer |
|
|
10
|
+
| --------------------- | --------------------------------------------------------------- | -------------------------------------- |
|
|
11
|
+
| package root | Lighthouse policy, release ladder, visual battery | None |
|
|
12
|
+
| `/chromium` | Renderer and mandatory mute launch profile | None |
|
|
13
|
+
| `/silent-qa` | Application-side runtime mute request and readiness marker | None |
|
|
14
|
+
| `/playwright` | Device tiers, isolated ports, preview server, silent navigation | `@playwright/test` |
|
|
15
|
+
| `/production-runtime` | Fresh server/browser lifecycle and runtime evidence | `@playwright/test` |
|
|
16
|
+
| `/vitest` | Vitest Browser Mode configuration | `vitest`, `@vitest/browser-playwright` |
|
|
17
|
+
| `/lighthouse` | Immutable Lighthouse CI presets | None |
|
|
18
|
+
| `/release-ladder` | Ordered sync/async verification steps | None |
|
|
19
|
+
| `/visual-battery` | Deterministic screenshot baseline orchestration | None |
|
|
20
|
+
|
|
21
|
+
The root deliberately does not re-export Playwright or Vitest integrations. A
|
|
22
|
+
consumer that needs only peer-free utilities therefore never loads or installs
|
|
23
|
+
either framework.
|
|
24
|
+
|
|
25
|
+
## Runtime evidence flow
|
|
26
|
+
|
|
27
|
+
1. `verifyProductionRuntime()` validates the URL, callbacks, server identity,
|
|
28
|
+
and lifecycle limits before starting external work.
|
|
29
|
+
2. When a server is configured, its readiness URL must be unreachable first.
|
|
30
|
+
The command is spawned directly with an argument array and must bind with
|
|
31
|
+
strict-port behavior.
|
|
32
|
+
3. A fresh browser launches with the selected renderer profile and exactly one
|
|
33
|
+
final `--mute-audio` argument.
|
|
34
|
+
4. Local-storage sentinels are installed before application code.
|
|
35
|
+
5. `openSilentGame()` adds the runtime-only mute query and waits for the
|
|
36
|
+
application readiness marker.
|
|
37
|
+
6. The consumer's `assertReady` proves game identity and primary UI. An optional
|
|
38
|
+
`assertSilentState` proves engine-specific mute state.
|
|
39
|
+
7. Console errors, page errors, failed requests, HTTP errors, and sentinel
|
|
40
|
+
mutations fail the verification.
|
|
41
|
+
8. Browser and owned server resources are closed on success or failure.
|
|
42
|
+
|
|
43
|
+
The browser launch argument is defense in depth. The application marker is the
|
|
44
|
+
primary proof that its own audio graph was muted before test interaction. The
|
|
45
|
+
mute adapter never changes the player's persisted preference.
|
|
46
|
+
|
|
47
|
+
## Visual baseline ownership
|
|
48
|
+
|
|
49
|
+
`runVisualBattery()` owns one canonical baseline directory. It discovers
|
|
50
|
+
top-level `*.browser.test.ts` and `*.browser.test.tsx` files deterministically,
|
|
51
|
+
runs selected harnesses in direct child processes, requires at least one PNG,
|
|
52
|
+
and asks Git for scoped status with argument arrays rather than shell commands.
|
|
53
|
+
|
|
54
|
+
Update mode reports reviewed changes. CI mode also refuses a dirty starting
|
|
55
|
+
state and fails on any resulting drift. Renderer-specific output belongs in an
|
|
56
|
+
explicit profile directory rather than behind a permissive pixel threshold.
|
|
57
|
+
|
|
58
|
+
## Configuration philosophy
|
|
59
|
+
|
|
60
|
+
Factories provide production-minded defaults and validate inputs at the public
|
|
61
|
+
boundary. Callers can override Playwright and browser-provider details, but the
|
|
62
|
+
silence guard is re-applied after merges. Values that would produce ambiguous
|
|
63
|
+
or empty verification—invalid ports, missing device tiers, empty browser
|
|
64
|
+
matrices, malformed URLs, zero Lighthouse runs, or zero screenshots—fail early
|
|
65
|
+
with actionable errors.
|
|
66
|
+
|
|
67
|
+
## Build and distribution
|
|
68
|
+
|
|
69
|
+
TypeScript emits independent ESM, CommonJS, and declaration trees. A nested
|
|
70
|
+
`dist/cjs/package.json` marks the CommonJS output without changing the package's
|
|
71
|
+
top-level ESM identity. Optional peers keep install boundaries narrow. Release
|
|
72
|
+
validation then checks:
|
|
73
|
+
|
|
74
|
+
- formatting, linting, strict types, and 100% reusable-library source coverage
|
|
75
|
+
(the two-line executable adapter is exercised through packed CLI smoke tests);
|
|
76
|
+
- both JavaScript module formats and declarations;
|
|
77
|
+
- package metadata and resolution through `publint` and
|
|
78
|
+
`@arethetypeswrong/cli`;
|
|
79
|
+
- clean tarball consumers for root, Playwright, production runtime, Vitest, and
|
|
80
|
+
CLI entry points;
|
|
81
|
+
- Linux, macOS, and Windows build/test portability in CI.
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Changelog
|
|
3
|
+
description: Release history generated and maintained by release-please.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Game Harness uses release-please and Conventional Commits to create release
|
|
7
|
+
pull requests, versions, GitHub Releases, and this project's changelog.
|
|
8
|
+
|
|
9
|
+
Read the current [CHANGELOG.md](https://github.com/jbcom/game-harness/blob/main/CHANGELOG.md).
|