diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index d774cea2..1fe3b073 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -1,12 +1,12 @@ name: e2e tests on: - # Nightly workflow - Run at 2 AM UTC daily + # Scheduled workflow + # 13:00 UTC ~= 8:00 AM ET (EST) + # 01:00 UTC ~= 8:00 PM ET (EST) schedule: - - cron: '0 2 * * *' - # Post-merge - Run after changes are merged to main - push: - branches: [main] + - cron: '0 13 * * *' + - cron: '0 1 * * *' # Manual trigger - Support workflow_dispatch for on-demand runs workflow_dispatch: inputs: @@ -32,9 +32,10 @@ on: type: string jobs: e2e-tests: + # E2E tests run only on the current LTS Node version for stability and speed. strategy: matrix: - node-version: [22.x, 24.x] + node-version: [22.x] runs-on: ubuntu-latest environment: e2e-dev timeout-minutes: 25 @@ -129,7 +130,7 @@ jobs: retention-days: 30 - name: Notify on Failure - if: failure() && (github.event_name == 'schedule' || github.event_name == 'push') && steps.check-secrets.outputs.has-secrets == 'true' + if: failure() && github.event_name == 'schedule' && steps.check-secrets.outputs.has-secrets == 'true' uses: actions/github-script@v7 with: script: | diff --git a/packages/b2c-cli/test/functional/e2e/sites-operations.test.ts b/packages/b2c-cli/test/functional/e2e/sites-operations.test.ts index ccb9f95a..0d026802 100644 --- a/packages/b2c-cli/test/functional/e2e/sites-operations.test.ts +++ b/packages/b2c-cli/test/functional/e2e/sites-operations.test.ts @@ -27,6 +27,12 @@ describe('Sites Operations E2E Tests', function () { let serverHostname: string; let ownSandboxId: null | string = null; + async function sleep(ms: number): Promise { + await new Promise((resolve) => { + setTimeout(resolve, ms); + }); + } + before(async function () { if (!process.env.SFCC_CLIENT_ID || !process.env.SFCC_CLIENT_SECRET) { this.skip(); @@ -66,13 +72,32 @@ describe('Sites Operations E2E Tests', function () { console.log(`Created dedicated sandbox ${ownSandboxId} at ${serverHostname}`); } - const importResult = await runCLI(['job', 'import', SITE_ARCHIVE_PATH, '--server', serverHostname]); + async function runImportWithRetry(remainingRetries: number) { + const importResult = await runCLI(['job', 'import', SITE_ARCHIVE_PATH, '--server', serverHostname]); + if (importResult.exitCode === 0) return importResult; + + const msg = importResult.stderr || importResult.stdout; + const isTransient = /fetch failed|ECONNRESET|ETIMEDOUT/i.test(msg); + + if (!isTransient || remainingRetries <= 0) return importResult; + + console.warn( + `Sites E2E: transient import error, retrying after delay (remaining retries: ${remainingRetries}):`, + msg, + ); + await sleep(2000); + return runImportWithRetry(remainingRetries - 1); + } + + const importResult = await runImportWithRetry(2); if (importResult.exitCode !== 0) { const msg = importResult.stderr || importResult.stdout; // If the sandbox/client lacks permissions, treat this as a valid customer scenario - // and skip the suite rather than failing in before(). - if (/not\s+allowed|unauthorized|forbidden|401|403/i.test(msg)) { + // and skip the suite rather than failing in before(). Also skip on transient + // network issues where the underlying HTTP fetch fails. + if (/not\s+allowed|unauthorized|forbidden|401|403|fetch failed/i.test(msg)) { + console.warn('Sites E2E: skipping suite due to import error:', msg); this.skip(); } expect(importResult.exitCode).to.equal(0, msg); diff --git a/packages/b2c-cli/test/functional/e2e/webdav-operations.test.ts b/packages/b2c-cli/test/functional/e2e/webdav-operations.test.ts index eeeaac3d..3a757632 100644 --- a/packages/b2c-cli/test/functional/e2e/webdav-operations.test.ts +++ b/packages/b2c-cli/test/functional/e2e/webdav-operations.test.ts @@ -368,7 +368,7 @@ describe('WebDAV Operations E2E Tests', function () { if (result.exitCode !== 0) return false; const response = JSON.parse(result.stdout); return response.entries?.some((e: any) => entryName(e) === testFileName); - }); + }, 300_000); }); }); });