Isolate Core E2E runtime state

Contract-Neutral: Core E2E scheduling and mock-readiness harness change; no deployment, installability, or runtime contract delta
This commit is contained in:
rcourtman
2026-07-20 00:14:37 +01:00
parent d7eb4457c4
commit dbd4b14b73
3 changed files with 91 additions and 5 deletions
+4 -3
View File
@@ -34,13 +34,13 @@ permissions:
jobs:
e2e:
name: Playwright Core E2E (shard ${{ matrix.shard }}/4)
name: Playwright Core E2E (shard ${{ matrix.shard }}/8)
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
shard: [1, 2, 3, 4, 5, 6, 7, 8]
steps:
- name: Checkout code
@@ -83,8 +83,9 @@ jobs:
PULSE_E2E_SKIP_DOCKER: "true"
PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL: "true"
PULSE_E2E_PERF: "1"
PULSE_E2E_REQUIRE_DEFAULT_MOCK_READY: "true"
PULSE_MULTI_TENANT_ENABLED: "true"
run: npm test -- --shard=${{ matrix.shard }}/4
run: npm test -- --shard=${{ matrix.shard }}/8
- name: Collect container logs
if: always()
@@ -257,7 +257,9 @@ test.describe('Platform pages shell', () => {
{ path: '/docker/images', testId: 'docker-page' },
{ path: '/docker/storage', testId: 'docker-page' },
{ path: '/docker/networks', testId: 'docker-page' },
{ path: '/docker/swarm', testId: 'docker-page' },
// The default mock inventory intentionally has no Swarm resources.
// Empty inventory tabs omit search because there is nothing actionable
// to filter; populated Swarm table toolbars have component coverage.
{ path: '/kubernetes/nodes', testId: 'kubernetes-page' },
{ path: '/kubernetes/workloads', testId: 'kubernetes-page' },
{ path: '/kubernetes/services', testId: 'kubernetes-page' },
+84 -1
View File
@@ -1112,9 +1112,14 @@ async function storageStateHasLiveSession(
baseURL: preferredBrowserBaseURL(),
storageState: statePath,
});
const page = await context.newPage();
try {
const res = await context.request.get("/api/state");
return res.status() === 200;
if (res.status() !== 200) {
return false;
}
await waitForDefaultMockRuntimeReady(page);
return true;
} catch {
return false;
} finally {
@@ -1122,6 +1127,83 @@ async function storageStateHasLiveSession(
}
}
type E2ERuntimeStateResource = {
name?: string;
sources?: string[];
type?: string;
};
type E2ERuntimeState = {
connectedInfrastructure?: Array<{ name?: string }>;
resources?: E2ERuntimeStateResource[];
};
const requiresDefaultMockRuntimeReadiness = (): boolean =>
["1", "true", "yes", "on"].includes(
String(process.env.PULSE_E2E_REQUIRE_DEFAULT_MOCK_READY || "")
.trim()
.toLowerCase(),
);
const resourceHasSource = (
resource: E2ERuntimeStateResource,
source: string,
): boolean => resource.sources?.includes(source) === true;
async function waitForDefaultMockRuntimeReady(page: Page): Promise<void> {
if (!requiresDefaultMockRuntimeReadiness()) {
return;
}
await expect
.poll(
async () => {
const response = await page.request.get("/api/state").catch(() => null);
if (!response?.ok()) {
return false;
}
const state = (await response.json()) as E2ERuntimeState;
const resources = Array.isArray(state.resources) ? state.resources : [];
const infrastructure = Array.isArray(state.connectedInfrastructure)
? state.connectedInfrastructure
: [];
return (
resources.some(
(resource) =>
resource.name === "nvme-primary" &&
resource.type === "storage" &&
resourceHasSource(resource, "vmware"),
) &&
resources.some(
(resource) =>
resource.type === "k8s-cluster" &&
resourceHasSource(resource, "kubernetes"),
) &&
resources.some(
(resource) =>
resource.name === "tank" &&
resourceHasSource(resource, "truenas"),
) &&
resources.some((resource) => resource.type === "docker-host") &&
resources.some((resource) => resource.type === "pbs") &&
resources.some((resource) => resource.type === "pmg") &&
infrastructure.some(
(entry) => entry.name === "esxi-01.lab.local",
)
);
},
{
message:
"default mock inventory should be complete before browser fixtures start",
timeout: 120_000,
intervals: [1_000, 2_000, 5_000],
},
)
.toBe(true);
}
export async function createAuthenticatedStorageState(
browser: Browser,
storageStatePath: string,
@@ -1144,6 +1226,7 @@ export async function createAuthenticatedStorageState(
const page = await context.newPage();
try {
await ensureSessionAuthenticated(page);
await waitForDefaultMockRuntimeReady(page);
await context.storageState({ path: storageStatePath });
} finally {
await context.close();