Skip to content

[ENG-2103] add E2E test helper class with CLI runner, retry utilities…#433

Open
leehpham wants to merge 2 commits intoproj/e2e-testsfrom
feat/ENG-2103
Open

[ENG-2103] add E2E test helper class with CLI runner, retry utilities…#433
leehpham wants to merge 2 commits intoproj/e2e-testsfrom
feat/ENG-2103

Conversation

@leehpham
Copy link
Copy Markdown
Member

…, and BrvE2EHelper.

Summary

  • Problem:
  • Why it matters:
  • What changed:
  • What did NOT change (scope boundary):

Type of change

  • Bug fix
  • New feature
  • Refactor (no behavior change)
  • Documentation
  • Test
  • Chore (build, dependencies, CI)

Scope (select all touched areas)

  • TUI / REPL
  • Agent / Tools
  • LLM Providers
  • Server / Daemon
  • Shared (constants, types, transport events)
  • CLI Commands (oclif)
  • Hub / Connectors
  • Cloud Sync
  • CI/CD / Infra

Linked issues

  • Closes #
  • Related #

Root cause (bug fixes only, otherwise write N/A)

  • Root cause:
  • Why this was not caught earlier:

Test plan

  • Coverage added:
    • Unit test
    • Integration test
    • Manual verification only
  • Test file(s):
  • Key scenario(s) covered:

User-visible changes

List user-visible changes (including defaults, config, or CLI output).
If none, write None.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording

Checklist

  • Tests added or updated and passing (npm test)
  • Lint passes (npm run lint)
  • Type check passes (npm run typecheck)
  • Build succeeds (npm run build)
  • Commits follow Conventional Commits format
  • Documentation updated (if applicable)
  • No breaking changes (or clearly documented above)
  • Branch is up to date with main

Risks and mitigations

List real risks for this PR. If none, write None.

  • Risk:
    • Mitigation:

@leehpham leehpham requested a review from bao-byterover as a code owner April 16, 2026 10:34
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 16, 2026

PR Review: [ENG-2103] E2E Test Helpers

  • Gather context and read changed files
  • Review retry.ts and retry.test.ts
  • Review cli-runner.ts and cli-runner.test.ts
  • Review brv-e2e-helper.ts and brv-e2e-helper.test.ts
  • Review helpers/index.ts
  • Post inline comments
  • Post overall summary with score

View job run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 16, 2026

Claude finished @leehpham's task in 2m 45s —— View job


PR Review: [ENG-2103] E2E Test Helpers

  • Gather context and read changed files
  • Review retry.ts and retry.test.ts
  • Review cli-runner.ts and cli-runner.test.ts
  • Review brv-e2e-helper.ts and brv-e2e-helper.test.ts
  • Review helpers/index.ts
  • Post inline comments
  • Post overall summary with score

Summary

Score: 4 / 5

Solid E2E test infrastructure. The design is clean — BrvE2eHelper encapsulates temp-dir lifecycle, LIFO teardowns, and JSON parsing in a way that will compose well in real E2E suites. retry and waitUntil are correct, well-tested, and the ESLint disable comments are appropriate.

Issues to fix before merge:

Severity Location Issue
issue cli-runner.ts:45 resolve Promise parameter shadows the node:path import — rename to fulfill
issue brv-e2e-helper.ts:114 setup() is not idempotent — calling it twice orphans the first temp directory
issue brv-e2e-helper.test.ts:49 Hardcoded '0.0.1' version string — use imported BRV_CONFIG_VERSION constant
nitpick cli-runner.ts:24 Hardcoded tsx/dist/esm/index.mjs internal path is fragile against tsx version bumps
nitpick retry.test.ts:70 Timing assertion with 20% margin can fail on slow CI — widen or restructure
nitpick brv-e2e-helper.test.ts:19 Describe block name 'BrvE2EHelper' doesn't match class BrvE2eHelper

What's good:

  • retry and waitUntil are correct and have excellent coverage (6 + 3 tests)
  • LIFO teardown ordering with swallowed errors is the right design for cleanup
  • runJson scanning from the last line backwards handles CLI preamble output gracefully
  • The before(requireE2eEnv) guard correctly skips auth tests when no E2E key is present
  • Barrel index.ts exports all public surface cleanly
  • process.env spread into child env with config override is the right layering approach

// so tsx resolves correctly regardless of the child process cwd
const nodeArgs = ['--import', TSX_IMPORT_PATH, '--no-warnings', BIN_DEV_PATH, ...args]

return new Promise((resolve) => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (naming/shadowing): The Promise callback parameter resolve shadows the resolve import from node:path. This will likely trigger the no-shadow ESLint rule and is genuinely confusing — a reader seeing resolve({exitCode, ...}) on line 52 has to mentally track which resolve is in scope.

Suggested change
return new Promise((resolve) => {
return new Promise((fulfill) => {

Then update lines 52 and 58 to use fulfill(...) instead. Fix this →

const PROJECT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
const BIN_DEV_PATH = resolve(PROJECT_ROOT, 'bin', 'dev.js')
// Resolve tsx from project root so it works even when cwd is a temp dir
const TSX_IMPORT_PATH = resolve(PROJECT_ROOT, 'node_modules', 'tsx', 'dist', 'esm', 'index.mjs')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (fragile path): Hardcoding tsx/dist/esm/index.mjs couples the runner to tsx's internal directory layout. If tsx restructures its package (as it has done between versions), this silently breaks E2E runs.

Consider resolving via the bin field instead:

// More resilient: find tsx via its package.json#bin map
const TSX_BIN_PATH = resolve(PROJECT_ROOT, 'node_modules', '.bin', 'tsx')

Or use require.resolve/import.meta.resolve with a try-catch fallback. Either approach survives tsx version bumps.

throw new Error(`No valid JSON found in CLI output.\nstdout: ${result.stdout}\nstderr: ${result.stderr}`)
}

async setup(): Promise<void> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (resource leak): setup() is not idempotent. Calling it twice creates a new temp dir and overwrites _cwd, orphaning the first directory without cleaning it up.

Add a guard (or call cleanup() first) to prevent the leak:

Suggested change
async setup(): Promise<void> {
async setup(): Promise<void> {
if (this._cwd) {
await this.cleanup()
}
const dir = realpathSync(mkdtempSync(join(tmpdir(), 'brv-e2e-')))

Alternatively, simply throw if already set up — forcing explicit cleanup before re-use. Fix this →

expect(existsSync(configPath)).to.be.true

const config = JSON.parse(readFileSync(configPath, 'utf8'))
expect(config).to.deep.equal({version: '0.0.1'})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (magic string): Hardcoding '0.0.1' couples this test to the constant's current value. If BRV_CONFIG_VERSION is bumped, this test breaks with a confusing mismatch. Use the imported constant directly:

Suggested change
expect(config).to.deep.equal({version: '0.0.1'})
expect(config).to.deep.equal({version: BRV_CONFIG_VERSION})

BRV_CONFIG_VERSION is already imported at line 8 of brv-e2e-helper.ts — add it to this test's imports too. Fix this →

const elapsed = Date.now() - start

// 2 retries * 50ms delay = at least 100ms
expect(elapsed).to.be.at.least(80) // small margin for timer imprecision
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (flaky timing test): The 80ms lower bound on a 100ms expected delay is only a 20% margin. On a heavily loaded CI runner this can fail spuriously. A slightly tighter lower bound assertion (e.g., at.least(60)) or switching to counting calls instead of timing would make this more reliable.

Alternatively, verify that the delay utility is called rather than relying on wall-clock elapsed time — but since sleep is a module-local unexported function that pattern isn't practical here. Widening the margin to at.least(50) is the safest path.

webAppUrl: 'http://localhost:0',
}

describe('BrvE2EHelper', () => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick (naming inconsistency): The describe block uses 'BrvE2EHelper' but the class is BrvE2eHelper. Minor, but matching the actual class name reduces confusion when searching logs or test output.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant