From c49d156e7ea77e2bf64e2d62ba173b4b4479c2d4 Mon Sep 17 00:00:00 2001 From: Shrinath Rao Date: Wed, 29 Jul 2026 16:36:47 +0530 Subject: [PATCH] test(alpha1): multi-org sharding for the alpha1 e2e run (#13530) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Companion OSS change to enterprise 2278 (alpha1 workflow). Same branch slug on both repos. ## What Supports running the Alpha1 (cloud) e2e matrix with its shards **divided across multiple orgs** for data isolation, on top of the shared-auth login (one Dex login, reused by all shards). Both changes are in \`global-setup-alpha1.js\`. ## Changes 1. **Ingest into the shard's own org (\`ORGNAME\`)** instead of the org baked into \`cloud-config.json\`. The shared-auth barrier logs into one org; without this, every shard ingested into that one org even when \`ORGNAME\` differed. 2. **Re-fetch the shard's own org passcode in the shared-auth path.** Each org has its own \`o2oi_\` default ingestion token (see \`core/organization.rs\` \`get_passcode\`). The barrier captured only its org's token, so shards on other orgs got \`401 Unauthorized Access\` on ingest. \`verifySharedAuth\` now calls \`fetchCloudConfig(page)\` to overwrite \`cloud-config.json\` with the current shard's passcode (fetched via the session, which is a member of every org). Keeps the single shared login — no login storm — while giving each shard the correct per-org credential. ## Context Part of the alpha1 parallelization effort: - Shared-auth artifact removes the 13-way concurrent Dex login storm (enterprise 2278). - Shards divided across 6 orgs (enterprise 2278 matrix) for data isolation. - These two OSS fixes make per-org ingestion actually work. ## Testing Validated by dispatching the enterprise \`playwright_alpha1.yml\` with \`o2_opensource_repo=test/alpha1-parallel-shards\`. Verifying each shard now ingests \`e2e_automate\` into its own org with \`status:200\` (previously \`401\` for non-barrier orgs). --- .../utils/enhanced-baseFixtures.js | 8 ++++- .../utils/global-setup-alpha1.js | 35 ++++++++++++------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/tests/ui-testing/playwright-tests/utils/enhanced-baseFixtures.js b/tests/ui-testing/playwright-tests/utils/enhanced-baseFixtures.js index add350bad4..a173514a82 100644 --- a/tests/ui-testing/playwright-tests/utils/enhanced-baseFixtures.js +++ b/tests/ui-testing/playwright-tests/utils/enhanced-baseFixtures.js @@ -128,7 +128,13 @@ async function verifyAuthentication(page) { * @param {import('@playwright/test').Page} page */ async function navigateToBase(page) { - const baseUrlWithOrg = `${process.env["ZO_BASE_URL"]}?org_identifier=${process.env["ORGNAME"]}`; + // Must include the /web/ SPA path. Navigating to the bare domain + // (`${ZO_BASE_URL}?org_identifier=X`) redirects to /web/ and DROPS the query + // string, so the app falls back to the user's DEFAULT org instead of ORGNAME. + // On cloud that default org can be a different, trial-expired org, which then + // redirects to /web/billings/plans where the home menu never renders and auth + // verification fails. Every other navigation in the suite already uses /web/. + const baseUrlWithOrg = `${process.env["ZO_BASE_URL"]}/web/?org_identifier=${process.env["ORGNAME"]}`; testLogger.info('Navigating to base URL with org identifier', { url: baseUrlWithOrg }); // Use 60s navigation timeout for all environments (dev/staging can be slow to load) diff --git a/tests/ui-testing/playwright-tests/utils/global-setup-alpha1.js b/tests/ui-testing/playwright-tests/utils/global-setup-alpha1.js index 738057c776..c68ecf0d82 100644 --- a/tests/ui-testing/playwright-tests/utils/global-setup-alpha1.js +++ b/tests/ui-testing/playwright-tests/utils/global-setup-alpha1.js @@ -484,7 +484,11 @@ async function performGlobalIngestion(page) { try { const cloudConfig = JSON.parse(fs.readFileSync(CLOUD_CONFIG_FILE, 'utf-8')); - orgId = cloudConfig.orgIdentifier; + // Ingest into THIS shard's org (ORGNAME) rather than the org baked into + // cloud-config.json by the shared-auth barrier. The passcode is user-level + // (identity), so it authorizes any org the user is a member of — only the + // target org differs per shard. Enables per-shard org isolation. + orgId = process.env.ORGNAME || cloudConfig.orgIdentifier; const basicAuth = Buffer.from(`${cloudConfig.userEmail}:${cloudConfig.passcode}`).toString('base64'); headers = { 'Authorization': `Basic ${basicAuth}`, @@ -597,16 +601,19 @@ async function verifySharedAuth(baseUrl) { await menuItem.waitFor({ state: 'visible', timeout: 15000 }); testLogger.info('[alpha1] Shared auth verified — menu visible'); - // Re-apply org switch + persist if active org doesn't match target - if (targetOrg && targetOrg !== 'default') { - const activeOrg = new URL(page.url()).searchParams.get('org_identifier'); - if (activeOrg !== targetOrg) { - testLogger.info(`[alpha1] Active org (${activeOrg}) != target (${targetOrg}); re-switching`); - await switchOrgViaDropdown(page, targetOrg); - await context.storageState({ path: AUTH_FILE }); - testLogger.info(`[alpha1] Auth state re-saved with target org active`); - } - } + // Note: no dropdown org-switch needed here. Each test pins its own org by + // navigating to /web/?org_identifier= (see navigateToBase), which the + // app honours on a fresh page load. Forcing a dropdown switch here is both + // redundant and flaky (the virtualized selector under load), so we rely on the + // per-test URL instead. The passcode below is still fetched per shard org. + + // Re-fetch THIS shard's own org passcode via the session. The downloaded + // cloud-config.json holds the shared-auth barrier org's ingestion token, + // which 401s against any other org — each org has its own o2oi_ default + // ingestion token (see core/organization.rs get_passcode). Overwrite + // cloud-config.json with ORGNAME's passcode so per-shard ingestion succeeds. + await fetchCloudConfig(page); + return true; } catch (e) { testLogger.warn(`[alpha1] Shared auth verification error: ${e.message}`); @@ -628,7 +635,11 @@ async function performGlobalIngestionWithFetch() { try { const cloudConfig = JSON.parse(fs.readFileSync(CLOUD_CONFIG_FILE, 'utf-8')); - orgId = cloudConfig.orgIdentifier; + // Ingest into THIS shard's org (ORGNAME) rather than the org baked into + // cloud-config.json by the shared-auth barrier. The passcode is user-level + // (identity), so it authorizes any org the user is a member of — only the + // target org differs per shard. Enables per-shard org isolation. + orgId = process.env.ORGNAME || cloudConfig.orgIdentifier; const basicAuth = Buffer.from(`${cloudConfig.userEmail}:${cloudConfig.passcode}`).toString('base64'); headers = { 'Authorization': `Basic ${basicAuth}`,