Skip to content

Commit 8eedacb

Browse files
authored
Restore test reporter and fix flaky e2e test issues (#97)
* restore test reporter and fix flaky e2e test issues * restore test reporter * fixing the flaky test * fixing error * restore test reporter
1 parent 96aa8c3 commit 8eedacb

12 files changed

Lines changed: 717 additions & 447 deletions

.github/workflows/e2e-tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
node-version: [22.x]
3939
runs-on: ubuntu-latest
4040
environment: e2e-dev
41-
timeout-minutes: 25
41+
timeout-minutes: 40
4242
steps:
4343
- uses: actions/checkout@v4
4444
- uses: actions/setup-node@v4
@@ -110,8 +110,8 @@ jobs:
110110
echo "Running E2E tests with realm: ${TEST_REALM}"
111111
echo "Node version: $(node --version)"
112112
113-
# Run E2E tests with JSON reporter for test results
114-
pnpm run test:e2e && pnpm run lint
113+
# Run E2E tests with CI reporter (outputs test-results.json)
114+
pnpm run test:e2e:ci && pnpm run lint
115115
116116
- name: E2E Test Report
117117
uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0

packages/b2c-cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@
265265
"test:ci": "c8 env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" --reporter json --reporter-option output=test-results.json \"test/**/*.test.ts\"",
266266
"test:unit": "env OCLIF_TEST_ROOT=. mocha --forbid-only --exclude \"test/functional/e2e/**\" \"test/**/*.test.ts\"",
267267
"test:agent": "env OCLIF_TEST_ROOT=. mocha --forbid-only --reporter min --exclude \"test/functional/e2e/**\" \"test/**/*.test.ts\"",
268-
"test:e2e": "env TEST_USE_SHARED_SANDBOX=true OCLIF_TEST_ROOT=. mocha --forbid-only --config test/functional/e2e/.mocharc.json \"test/functional/e2e/**/*.test.ts\"",
268+
"test:e2e": "env TEST_USE_SHARED_SANDBOX=true OCLIF_TEST_ROOT=. mocha --forbid-only --require test/functional/e2e/hooks.ts --node-option import=tsx --timeout 30000 --retries 2 --reporter spec \"test/functional/e2e/**/*.test.ts\"",
269+
"test:e2e:ci": "env TEST_USE_SHARED_SANDBOX=true OCLIF_TEST_ROOT=. mocha --forbid-only --require test/functional/e2e/hooks.ts --node-option import=tsx --timeout 30000 --retries 2 --reporter json --reporter-option output=test-results.json \"test/functional/e2e/**/*.test.ts\"",
269270
"test:e2e:auth": "env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/functional/e2e/auth-token.test.ts\"",
270271
"test:e2e:code": "env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/functional/e2e/code-lifecycle.test.ts\"",
271272
"test:e2e:jobs": "env OCLIF_TEST_ROOT=. mocha --forbid-only \"test/functional/e2e/job-execution.test.ts\"",

packages/b2c-cli/test/functional/e2e/.mocharc.json

Lines changed: 0 additions & 11 deletions
This file was deleted.

packages/b2c-cli/test/functional/e2e/auth-token.test.ts

Lines changed: 21 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,21 @@
55
*/
66

77
import {expect} from 'chai';
8-
import {execa} from 'execa';
9-
import path from 'node:path';
10-
import {fileURLToPath} from 'node:url';
11-
12-
const __filename = fileURLToPath(import.meta.url);
13-
const __dirname = path.dirname(__filename);
8+
import {parseJSONOutput, runCLIWithRetry, TIMEOUTS, toString} from './test-utils.js';
149

1510
/**
1611
* E2E Tests for Authentication Token Generation
1712
*/
1813
describe('Auth Token E2E Tests', function () {
19-
this.timeout(120_000); // 2 minutes
14+
this.timeout(TIMEOUTS.AUTH * 8); // 2 minutes
2015
this.retries(2);
2116

22-
const CLI_BIN = path.resolve(__dirname, '../../../bin/run.js');
23-
2417
before(function () {
2518
if (!process.env.SFCC_CLIENT_ID || !process.env.SFCC_CLIENT_SECRET) {
2619
this.skip();
2720
}
2821
});
2922

30-
async function runCLI(args: string[], env?: Record<string, string>) {
31-
const result = await execa('node', [CLI_BIN, ...args], {
32-
env: {
33-
...process.env,
34-
...env,
35-
SFCC_LOG_LEVEL: 'silent',
36-
},
37-
reject: false,
38-
});
39-
return result;
40-
}
41-
4223
function decodeJWT(token: string): Record<string, unknown> {
4324
const parts = token.split('.');
4425
if (parts.length !== 3) {
@@ -49,11 +30,11 @@ describe('Auth Token E2E Tests', function () {
4930
}
5031

5132
it('should generate a valid OAuth token with correct format, scopes, and expiration', async function () {
52-
const result = await runCLI(['auth:token', '--json']);
53-
expect(result.exitCode).to.equal(0, `Token generation failed: ${result.stderr}`);
33+
const result = await runCLIWithRetry(['auth:token', '--json'], {timeout: TIMEOUTS.AUTH, verbose: true});
34+
expect(result.exitCode, `Token generation failed: ${toString(result.stderr)}`).to.equal(0);
5435
expect(result.stdout).to.not.be.empty;
5536

56-
const response = JSON.parse(result.stdout);
37+
const response = parseJSONOutput(result);
5738
expect(response).to.be.an('object');
5839
expect(response.accessToken).to.be.a('string').and.not.be.empty;
5940
expect(response.expires).to.be.a('string');
@@ -86,11 +67,13 @@ describe('Auth Token E2E Tests', function () {
8667
// Use only scopes your client actually has
8768
const extraScopes = ['profile', 'roles'];
8869

89-
const result = await runCLI(['auth:token', '--scope', extraScopes.join(','), '--json']);
70+
const result = await runCLIWithRetry(['auth:token', '--scope', extraScopes.join(','), '--json'], {
71+
timeout: TIMEOUTS.AUTH,
72+
});
9073

91-
expect(result.exitCode).to.equal(0, `Token generation with extra scopes failed: ${result.stderr}`);
74+
expect(result.exitCode, `Token generation with extra scopes failed: ${toString(result.stderr)}`).to.equal(0);
9275

93-
const response = JSON.parse(result.stdout);
76+
const response = parseJSONOutput(result);
9477
const accessToken = response.accessToken as string;
9578
expect(accessToken).to.be.a('string').and.not.be.empty;
9679
expect(response.scopes).to.include.members(extraScopes);
@@ -110,9 +93,12 @@ describe('Auth Token E2E Tests', function () {
11093

11194
describe('Invalid Credentials', function () {
11295
it('should fail with invalid client credentials', async function () {
113-
const result = await runCLI(['auth:token', '--json'], {
114-
SFCC_CLIENT_ID: 'invalid-client-id',
115-
SFCC_CLIENT_SECRET: 'invalid-client-secret',
96+
const result = await runCLIWithRetry(['auth:token', '--json'], {
97+
timeout: TIMEOUTS.AUTH,
98+
env: {
99+
SFCC_CLIENT_ID: 'invalid-client-id',
100+
SFCC_CLIENT_SECRET: 'invalid-client-secret',
101+
},
116102
});
117103

118104
expect(result.exitCode).to.not.equal(0);
@@ -123,23 +109,23 @@ describe('Auth Token E2E Tests', function () {
123109

124110
describe('JSON Output Structure', function () {
125111
it('should return correct JSON keys', async function () {
126-
const result = await runCLI(['auth:token', '--json']);
127-
const response = JSON.parse(result.stdout);
112+
const result = await runCLIWithRetry(['auth:token', '--json'], {timeout: TIMEOUTS.AUTH});
113+
const response = parseJSONOutput(result);
128114
expect(response).to.have.all.keys('accessToken', 'expires', 'scopes');
129115
});
130116
});
131117

132118
describe('Default Scopes', function () {
133119
it('should return default scopes when no scopes are requested', async function () {
134-
const result = await runCLI(['auth:token', '--json']);
135-
const response = JSON.parse(result.stdout);
120+
const result = await runCLIWithRetry(['auth:token', '--json'], {timeout: TIMEOUTS.AUTH});
121+
const response = parseJSONOutput(result) as {scopes: string[]};
136122
expect(response.scopes.length).to.be.greaterThan(0);
137123
});
138124
});
139125

140126
describe('Non-JSON Output', function () {
141127
it('should output raw token in non-JSON mode', async function () {
142-
const result = await runCLI(['auth:token']);
128+
const result = await runCLIWithRetry(['auth:token'], {timeout: TIMEOUTS.AUTH});
143129
expect(result.exitCode).to.equal(0);
144130
expect(result.stdout).to.match(/^ey[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+\.[A-Za-z0-9-_]+$/); // JWT regex
145131
});

packages/b2c-cli/test/functional/e2e/code-lifecycle.test.ts

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import * as fs from 'node:fs/promises';
1010
import path from 'node:path';
1111
import {fileURLToPath} from 'node:url';
1212
import {getSharedContext, hasSharedSandbox} from './shared-context.js';
13+
import {getSandboxId, getHostname, parseJSONOutput, runCLIWithRetry, TIMEOUTS, toString} from './test-utils.js';
1314

1415
const __filename = fileURLToPath(import.meta.url);
1516
const __dirname = path.dirname(__filename);
1617

1718
describe('Code Lifecycle E2E Tests', function () {
18-
this.timeout(900_000);
19+
this.timeout(TIMEOUTS.CODE_DEPLOY * 3); // 15 minutes
1920
this.retries(2);
2021

2122
const CLI_BIN = path.resolve(__dirname, '../../../bin/run.js');
@@ -42,33 +43,25 @@ describe('Code Lifecycle E2E Tests', function () {
4243
} else {
4344
// Fallback: Create own sandbox
4445
console.log('No shared sandbox available, creating dedicated sandbox for Code tests...');
45-
this.timeout(720_000); // 12 minutes for sandbox creation
46+
this.timeout(TIMEOUTS.ODS_OPERATION);
4647

4748
if (!process.env.TEST_REALM) {
4849
throw new Error('TEST_REALM required to create sandbox');
4950
}
5051

51-
const result = await runCLI(
52+
const result = await runCLIWithRetry(
5253
['ods', 'create', '--realm', process.env.TEST_REALM, '--ttl', '4', '--wait', '--set-permissions', '--json'],
53-
{timeout: 720_000},
54+
{timeout: TIMEOUTS.ODS_OPERATION, verbose: true},
5455
);
5556

56-
expect(result.exitCode).to.equal(0, `Failed to create sandbox: ${result.stderr}`);
57-
const sandbox = JSON.parse(result.stdout);
58-
ownSandboxId = sandbox.id;
59-
serverHostname = sandbox.hostName;
60-
console.log(`Created dedicated sandbox ${ownSandboxId} at ${serverHostname}`);
57+
expect(result.exitCode, `Failed to create sandbox: ${toString(result.stderr)}`).to.equal(0);
58+
const sandbox = parseJSONOutput(result);
59+
ownSandboxId = getSandboxId(sandbox);
60+
serverHostname = getHostname(sandbox);
61+
console.log(`Created dedicated sandbox ${ownSandboxId} at ${serverHostname}`);
6162
}
6263
});
6364

64-
async function runCLI(args: string[], options: {timeout?: number} = {}) {
65-
return execa('node', [CLI_BIN, ...args], {
66-
env: {...process.env, SFCC_LOG_LEVEL: 'silent'},
67-
reject: false,
68-
timeout: options.timeout,
69-
});
70-
}
71-
7265
after(async function () {
7366
this.timeout(180_000); // 3 minutes for cleanup
7467

@@ -78,13 +71,13 @@ describe('Code Lifecycle E2E Tests', function () {
7871

7972
// Delete remaining code versions
8073
if (codeVersionB && serverHostname) {
81-
await runCLI(['code', 'delete', codeVersionB, '--server', serverHostname, '--force']);
74+
await runCLIWithRetry(['code', 'delete', codeVersionB, '--server', serverHostname, '--force']);
8275
}
8376

8477
// Delete own sandbox if we created one
8578
if (ownSandboxId) {
8679
console.log(`Cleaning up dedicated sandbox ${ownSandboxId}...`);
87-
await runCLI(['ods', 'delete', ownSandboxId, '--force']);
80+
await runCLIWithRetry(['ods', 'delete', ownSandboxId, '--force']);
8881
console.log('Dedicated sandbox deleted');
8982
}
9083
});
@@ -94,7 +87,7 @@ describe('Code Lifecycle E2E Tests', function () {
9487
it('should deploy first code version', async function () {
9588
codeVersionA = `e2e-a-${Date.now()}`;
9689

97-
const result = await runCLI([
90+
const result = await runCLIWithRetry([
9891
'code',
9992
'deploy',
10093
CARTRIDGES_DIR,
@@ -105,24 +98,24 @@ describe('Code Lifecycle E2E Tests', function () {
10598
'--json',
10699
]);
107100

108-
expect(result.exitCode).to.equal(0, result.stderr);
101+
expect(result.exitCode).to.equal(0, toString(result.stderr));
109102
});
110103
});
111104

112105
describe('Step 2: Verify Code Version A in List', function () {
113106
it('should find code version A in list', async function () {
114-
const result = await runCLI(['code', 'list', '--server', serverHostname, '--json']);
107+
const result = await runCLIWithRetry(['code', 'list', '--server', serverHostname, '--json']);
115108
expect(result.exitCode).to.equal(0);
116109

117-
const response = JSON.parse(result.stdout);
110+
const response = parseJSONOutput(result);
118111
const found = response.data.find((v: any) => v.id === codeVersionA);
119112
expect(found).to.exist;
120113
});
121114
});
122115

123116
describe('Step 3: Activate Code Version A', function () {
124117
it('should activate version A', async function () {
125-
const result = await runCLI(['code', 'activate', codeVersionA, '--server', serverHostname, '--json']);
118+
const result = await runCLIWithRetry(['code', 'activate', codeVersionA, '--server', serverHostname, '--json']);
126119

127120
expect(result.exitCode).to.equal(0);
128121
});
@@ -132,7 +125,7 @@ describe('Code Lifecycle E2E Tests', function () {
132125
it('should deploy second code version', async function () {
133126
codeVersionB = `e2e-b-${Date.now()}`;
134127

135-
const result = await runCLI([
128+
const result = await runCLIWithRetry([
136129
'code',
137130
'deploy',
138131
CARTRIDGES_DIR,
@@ -149,27 +142,27 @@ describe('Code Lifecycle E2E Tests', function () {
149142

150143
describe('Step 5: Verify Code Version B in List', function () {
151144
it('should find code version B in list', async function () {
152-
const result = await runCLI(['code', 'list', '--server', serverHostname, '--json']);
145+
const result = await runCLIWithRetry(['code', 'list', '--server', serverHostname, '--json']);
153146
expect(result.exitCode).to.equal(0);
154147

155-
const response = JSON.parse(result.stdout);
148+
const response = parseJSONOutput(result);
156149
const found = response.data.find((v: any) => v.id === codeVersionB);
157150
expect(found).to.exist;
158151
});
159152
});
160153

161154
describe('Step 6: Activate Code Version B', function () {
162155
it('should activate version B (A becomes inactive)', async function () {
163-
const result = await runCLI(['code', 'activate', codeVersionB, '--server', serverHostname, '--json']);
156+
const result = await runCLIWithRetry(['code', 'activate', codeVersionB, '--server', serverHostname, '--json']);
164157

165158
expect(result.exitCode).to.equal(0);
166159
});
167160
});
168161

169162
describe('Step 7: Verify Active Code Version', function () {
170163
it('should show version B as active', async function () {
171-
const result = await runCLI(['code', 'list', '--server', serverHostname, '--json']);
172-
const response = JSON.parse(result.stdout);
164+
const result = await runCLIWithRetry(['code', 'list', '--server', serverHostname, '--json']);
165+
const response = parseJSONOutput(result);
173166

174167
const active = response.data.find((v: any) => v.active === true);
175168
expect(active.id).to.equal(codeVersionB);
@@ -208,21 +201,24 @@ describe('Code Lifecycle E2E Tests', function () {
208201
it('should delete inactive version A', async function () {
209202
console.log(`Starting deletion of code version: ${codeVersionA}`);
210203

211-
const result = await runCLI(['code', 'delete', codeVersionA, '--server', serverHostname, '--force', '--json'], {
212-
timeout: 120_000,
213-
}); // 2 minutes timeout
204+
const result = await runCLIWithRetry(
205+
['code', 'delete', codeVersionA, '--server', serverHostname, '--force', '--json'],
206+
{
207+
timeout: 120_000,
208+
},
209+
); // 2 minutes timeout
214210

215211
console.log(`Deletion finished with exit code: ${result.exitCode}`);
216212

217-
expect(result.exitCode).to.equal(0, `Delete failed: ${result.stderr}`);
213+
expect(result.exitCode).to.equal(0, `Delete failed: ${toString(result.stderr)}`);
218214
codeVersionA = '';
219215
});
220216
});
221217

222218
describe('Step 10: Verify Code Version A Removed', function () {
223219
it('should not find deleted version A', async function () {
224-
const result = await runCLI(['code', 'list', '--server', serverHostname, '--json']);
225-
const response = JSON.parse(result.stdout);
220+
const result = await runCLIWithRetry(['code', 'list', '--server', serverHostname, '--json']);
221+
const response = parseJSONOutput(result);
226222

227223
const found = response.data.find((v: any) => v.id === codeVersionA);
228224
expect(found).to.not.exist;

0 commit comments

Comments
 (0)