Skip to content

Add E2E Test Playground with SCW Release Test Suite#217

Merged
spencerstock merged 27 commits intomasterfrom
spencer/e2e-playground
Mar 26, 2026
Merged

Add E2E Test Playground with SCW Release Test Suite#217
spencerstock merged 27 commits intomasterfrom
spencer/e2e-playground

Conversation

@spencerstock
Copy link
Copy Markdown
Collaborator

@spencerstock spencerstock commented Dec 23, 2025

What changed? Why?

Added an E2E test playground with automated test runner for SDK functionality in the testapp. This includes:

  • New /e2e-test page in testapp with test runner UI
  • Test coverage for wallet connection, signing, transactions, payments, subscriptions, spend permissions, sub-accounts, and ProLink
  • Modular test architecture with hooks for state management, test execution, and result handling
  • SCW Release test suite with configurable wallet URL for testing against different environments
  • User interaction modal system for handling popup-based tests
  • SDK loader utility to switch between npm and local workspace SDK versions
  • Test result formatting and display with status tracking

Key features:

  • Run individual test categories or full test suite
  • Skip modal prompts for automated testing
  • Configurable delays and retry logic
  • Test result visualization and debugging support

How was this tested?

  • Manual testing of E2E test runner interface
  • Validation of SDK loader switching between npm/local versions
  • SCW Release test suite execution with custom wallet URLs
  • Test execution across all supported test categories

How can reviewers manually test these changes?

  1. Run testapp: yarn dev (from examples/testapp)
  2. Navigate to /e2e-test page
  3. Click "Run All Tests" to execute full test suite
  4. Or click individual category buttons to run specific test groups
  5. Observe test results with pass/fail status and execution details

For SCW Release testing:

  1. Set custom wallet URL in configuration
  2. Run "SCW Release Tests" button
  3. Verify tests complete with expected wallet behavior

…lpers

- Remove redundant addLog calls from test files (status updates already provide this info)
- Remove unused ensureConnection helper from useConnectionState
- Clean up imports and types across test framework
- Update documentation to reflect simplified logging approach
@cb-heimdall
Copy link
Copy Markdown
Collaborator

cb-heimdall commented Dec 23, 2025

✅ Heimdall Review Status

Requirement Status More Info
Reviews 2/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@spencerstock spencerstock marked this pull request as ready for review January 27, 2026 17:58
Comment on lines -58 to -59
<MenuButton colorScheme="telegram" as={Button} rightIcon={<ChevronDownIcon />}>
{`SDK: ${version}`}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

removing this since it currently does nothing

Copy link
Copy Markdown
Collaborator

@montycheese montycheese left a comment

Choose a reason for hiding this comment

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

Looks great, I tested locally and it'll be a great time saver for testing and smoke tests.

one clarifying question

chainIdHex: '0x14a34',
name: 'Base Sepolia',
rpcUrl:
'https://api.developer.coinbase.com/rpc/v1/base-sepolia/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

who owns this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

A team inside coinbase - but removing as it's not actually needed.

Copy link
Copy Markdown
Collaborator

@stephancill stephancill left a comment

Choose a reason for hiding this comment

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

was it your intention to replace the paymaster url here with a dummy one? there are a couple more instances that i didn't highlight

Comment on lines +42 to +47
transport: http(
'https://api.developer.coinbase.com/rpc/v1/base-sepolia/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O'
),
transport: http('https://example.paymaster.com'),
});
const bundlerClient = createBundlerClient({
account: subAccount,
client: client as Client,
transport: http(
'https://api.developer.coinbase.com/rpc/v1/base-sepolia/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O'
),
transport: http('https://example.paymaster.com'),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is this intentional?

Comment on lines +33 to +38
transport: http(
'https://api.developer.coinbase.com/rpc/v1/base-sepolia/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O'
),
transport: http('https://example.paymaster.com'),
});
const bundlerClient = createBundlerClient({
account: subAccount,
client: client as Client,
transport: http(
'https://api.developer.coinbase.com/rpc/v1/base-sepolia/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O'
),
transport: http('https://example.paymaster.com'),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

here too

capabilities: {
paymasterService: {
url: 'https://api.developer.coinbase.com/rpc/v1/base-sepolia/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O',
url: 'https://example.paymaster.com',
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

paymaster url

Resolve yarn.lock merge conflicts from master, carry forward upstream CI-related updates, and clarify that demo paymaster URLs in import-sub-account examples are intentional placeholders.

Made-with: Cursor
Copy link
Copy Markdown
Collaborator Author

@spencerstock spencerstock left a comment

Choose a reason for hiding this comment

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

🔴 Changes requested · 25/100

This PR adds a comprehensive E2E test playground for the SDK, which is well-architected with modular hooks, a test registry, and good separation of concerns. However, there is a critical production regression: the default bundlerUrl in packages/account-sdk/src/interface/payment/getPaymentStatus.ts was replaced with the non-functional https://example.paymaster.com. This breaks getPaymentStatus() for any SDK consumer not providing a custom bundlerUrl. The corresponding tests were also updated to expect this dummy URL, masking the bug with a false green CI signal.

The testapp-only changes (replacing hardcoded API keys with placeholder URLs in examples) are reasonable for secret hygiene, but the SDK library code change must be reverted or replaced with a functional default endpoint before merging.

The rest of the PR — the E2E test page, SDK loader, test runner hooks, test helpers, and UI components — all look solid and well-structured for their intended purpose as a test playground.

Confidence: 25/100  ·  🔴 2 critical

Pass 1 → Pass 2 full details

Pass 1 found 2 comments:

packages/account-sdk/src/interface/payment/getPaymentStatus.ts:61critical

Replacing the default bundlerUrl fallback with https://example.paymaster.com will cause getPaymentStatus to fail in production for any user who relies on the default configuration.

If the intention was to remove a leaked API key or avoid hardcoding a specific Coinbase Developer Platform URL, you must either:

  1. Provide a functional public default RPC/bundler URL.
  2. Make bundlerUrl a required parameter (though note this is a breaking API change).

As currently written, SDK users without a custom bundlerUrl will have their network requests silently sent to an invalid domain, breaking payment status checks.

Pass 2: Confirmed via retrieval. The file packages/account-sdk/src/interface/payment/getPaymentStatus.ts line 62 now reads bundlerUrl || (testnet ? 'https://example.paymaster.com' : 'https://example.paymaster.com'). This is production SDK library code, not test code. Any SDK consumer who calls getPaymentStatus() without providing a custom bundlerUrl will have their request sent to a non-functional domain. The tests were also updated to match this dummy URL, masking the regression. This is a clear critical bug.

✏️ examples/testapp/src/pages/e2e-test/tests/sub-account-features.ts:237suggestionnit

The E2E test hardcodes https://example.paymaster.com for the paymaster service capability. When this test is run against a real Smart Contract Wallet, the wallet will attempt to fetch paymaster data from this dummy URL, which will fail and consequently fail the E2E test suite.

Consider retrieving this from a configuration variable (e.g., exposed via ConfigContextProvider or an environment variable) so that developers running the tests can provide a real paymaster URL, similar to how scwUrl is provided.

Pass 2: The hardcoded https://example.paymaster.com in the E2E test file for sub-account features is within a test-only file in the testapp (examples/testapp). This is an E2E test playground, not production code. The test is expected to fail when run against a real wallet without a real paymaster URL, but that's the nature of an E2E test that requires external infrastructure. While making it configurable would be nice, it's not a significant issue since this is a test-only file and the pattern is consistent with the other placeholder URLs in the testapp. Adjusting to nit.

Pass 2 added 1 new finding:

🆕 packages/account-sdk/src/interface/payment/getPaymentStatus.test.ts:70critical

The test expectations were updated to match the dummy example.paymaster.com URL, which masks the production regression. These tests should be asserting against the real default bundler URLs (or the tests should be structured so the default URL is verified separately). As-is, the test suite gives a false green signal that the dummy URL is correct behavior.

🔧 Fix with prompt
A reviewer gave these comments as feedback. Validate them and fix all the ones that need to be fixed.

- [critical] packages/account-sdk/src/interface/payment/getPaymentStatus.ts:61-62
  **Critical: Production SDK regression — default bundlerUrl replaced with non-functional domain.**
  
  The default `bundlerUrl` fallback was changed from the Coinbase Developer Platform RPC endpoints to `https://example.paymaster.com`. This is production SDK library code: any consumer calling `getPaymentStatus()` without providing a custom `bundlerUrl` will have their `fetch()` sent to a non-functional domain, silently breaking payment status checks.
  
  If the intention was to remove a leaked API key:
  1. Provide a functional public default RPC/bundler URL.
  2. Or make `bundlerUrl` a required parameter (breaking API change).
  3. Or use an environment variable / configuration mechanism.
  
  As currently written, this breaks the default usage pattern documented in this file's JSDoc.

- [critical] packages/account-sdk/src/interface/payment/getPaymentStatus.test.ts:70-70
  **Critical: Tests updated to match the dummy URL, masking the production regression.**
  
  The test expectations were changed from the real Coinbase Developer Platform RPC URLs to `https://example.paymaster.com`. This means the test suite gives a false green signal — it appears to pass, but it's now asserting that the broken dummy URL is the expected default behavior. When the production URL is restored, these test expectations need to be restored as well.

SHA 44f5cc9a · gemini-3.1-pro-preview → claude-opus-4-6

Comment on lines +61 to +62
const effectiveBundlerUrl =
bundlerUrl ||
(testnet
? 'https://api.developer.coinbase.com/rpc/v1/base-sepolia/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O'
: 'https://api.developer.coinbase.com/rpc/v1/base/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O');
bundlerUrl || (testnet ? 'https://example.paymaster.com' : 'https://example.paymaster.com');
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

critical

Critical: Production SDK regression — default bundlerUrl replaced with non-functional domain.

The default bundlerUrl fallback was changed from the Coinbase Developer Platform RPC endpoints to https://example.paymaster.com. This is production SDK library code: any consumer calling getPaymentStatus() without providing a custom bundlerUrl will have their fetch() sent to a non-functional domain, silently breaking payment status checks.

If the intention was to remove a leaked API key:

  1. Provide a functional public default RPC/bundler URL.
  2. Or make bundlerUrl a required parameter (breaking API change).
  3. Or use an environment variable / configuration mechanism.

As currently written, this breaks the default usage pattern documented in this file's JSDoc.


expect(fetch).toHaveBeenCalledWith(
'https://api.developer.coinbase.com/rpc/v1/base/S-fOd2n2Oi4fl4e1Crm83XeDXZ7tkg8O',
'https://example.paymaster.com',
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

critical

Critical: Tests updated to match the dummy URL, masking the production regression.

The test expectations were changed from the real Coinbase Developer Platform RPC URLs to https://example.paymaster.com. This means the test suite gives a false green signal — it appears to pass, but it's now asserting that the broken dummy URL is the expected default behavior. When the production URL is restored, these test expectations need to be restored as well.

Copy link
Copy Markdown
Collaborator Author

@spencerstock spencerstock left a comment

Choose a reason for hiding this comment

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

🔴 Changes requested · 88/100 · Re-review

Re-review summary: both previously-blocking payment-status regressions in packages/account-sdk are now fixed with concrete code evidence. getPaymentStatus has restored functional Coinbase RPC defaults (base/base-sepolia) instead of the dummy domain, and getPaymentStatus.test.ts now asserts those real endpoints again, so the prior false-green test masking issue is resolved.

After validating those fixes, I ran a targeted sweep of new/changed e2e playground logic and found one new high-signal regression: SDK source switching UI does not actually reload the selected SDK implementation. The page updates sdkSource state when toggling Local/NPM, but no effect or handler call invokes loadAndInitializeSDK on source change, so tests continue to run against the previously loaded SDK despite the selected label. This breaks a core claimed feature of the PR (npm/local switching) and can produce misleading release test results.

Confidence: 88/100  ·  🔴 1 critical

🔧 Fix with prompt
A reviewer gave these comments as feedback. Validate them and fix all the ones that need to be fixed.

- [critical] examples/testapp/src/pages/e2e-test/index.page.tsx:182-185
  Switching the SDK source here only updates `sdkSource` state, but it never reloads the SDK instance. Since `loadAndInitializeSDK` is only called on mount and `scwUrl` changes, toggling Local/NPM in the UI leaves `loadedSDK`/`provider` from the previous source, so tests can run against the wrong SDK while the UI shows the new selection. This breaks the PR’s core npm-vs-local testing guarantee. Please trigger `loadAndInitializeSDK` on source change (either directly in `handleSourceChange` after `setSdkSource`, or via a `useEffect` keyed on `sdkSource`) and ensure provider/state are refreshed atomically.

SHA 84ae927a · gpt-5.3-codex

Comment on lines +182 to +185
loadAndInitializeSDK({ walletUrl: scwUrl });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [scwUrl]);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

critical

Switching the SDK source here only updates sdkSource state, but it never reloads the SDK instance. Since loadAndInitializeSDK is only called on mount and scwUrl changes, toggling Local/NPM in the UI leaves loadedSDK/provider from the previous source, so tests can run against the wrong SDK while the UI shows the new selection. This breaks the PR’s core npm-vs-local testing guarantee. Please trigger loadAndInitializeSDK on source change (either directly in handleSourceChange after setSdkSource, or via a useEffect keyed on sdkSource) and ensure provider/state are refreshed atomically.

Update next.config formatting to satisfy Biome and align pay unit tests with the new translatePaymentToSendCalls argument shape.

Made-with: Cursor
Re-add the invalid dataSuffix unit test and translatePaymentToSendCalls expectation removed by an earlier cleanup commit, eliminating unintended SDK test drift from this PR.

Made-with: Cursor
Ensure e2e SDK source toggles actually reload SDK/provider state and replace old noExplicitAny biome-ignore workarounds in RpcMethodCard with explicit narrowing for request/response data.

Made-with: Cursor
Copy link
Copy Markdown
Collaborator Author

@spencerstock spencerstock left a comment

Choose a reason for hiding this comment

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

🟡 Mostly good, a few suggestions · 88/100 · Re-review

Re-review result: both previously-blocking payment status regressions are now fixed in packages/account-sdk. The default getPaymentStatus() bundler fallback has been restored to Coinbase RPC endpoints (getPaymentStatus.ts), and the tests were updated back to assert those real defaults (getPaymentStatus.test.ts). I did a second-pass sweep over the new E2E runner code and found two net-new correctness issues: (1) async SDK load failures in index.page.tsx are currently unhandled in useEffect, which can produce unhandled promise rejections; and (2) completion toast pass/fail totals in useTestRunner are computed from stale captured state, so summaries can be wrong (often 0/0 or prior-run values). No regressions were found in the originally reviewed critical SDK payment-status path.

Confidence: 88/100  ·  🟡 2 suggestions

🔧 Fix with prompt
A reviewer gave these comments as feedback. Validate them and fix all the ones that need to be fixed.

- [suggestion] examples/testapp/src/pages/e2e-test/index.page.tsx:172-174
  `loadAndInitializeSDK()` can throw (it re-throws on load failures in `useSDKState`), but this effect does not handle rejection. That creates unhandled promise rejections and leaves users without visible failure feedback when npm/local SDK loading fails. Please wrap this in an async IIFE with `try/catch` (or `void loadAndInitializeSDK(...).catch(...)`) and surface `sdkLoadError`/toast feedback.

- [suggestion] examples/testapp/src/pages/e2e-test/hooks/useTestRunner.ts:371-378
  These pass/fail totals are read from `testState.testCategories` captured in the callback closure, so they can be stale at completion time (often showing 0/0 or previous-run counts). The same pattern appears again in `runSCWReleaseTests`'s `finally`. Prefer deriving counts from a local accumulator during execution, or reading from a ref that is kept in sync with latest categories/results before emitting completion toasts.

SHA 69fbee3b · gpt-5.3-codex

Comment on lines +172 to +174
useEffect(() => {
loadAndInitializeSDK({ walletUrl: scwUrl });
}, [loadAndInitializeSDK, scwUrl, sdkSource]);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

loadAndInitializeSDK() can throw (it re-throws on load failures in useSDKState), but this effect does not handle rejection. That creates unhandled promise rejections and leaves users without visible failure feedback when npm/local SDK loading fails. Please wrap this in an async IIFE with try/catch (or void loadAndInitializeSDK(...).catch(...)) and surface sdkLoadError/toast feedback.

Comment on lines +371 to +378
const passed = testState.testCategories.reduce(
(acc, cat) => acc + cat.tests.filter((t) => t.status === 'passed').length,
0
);
const failed = testState.testCategories.reduce(
(acc, cat) => acc + cat.tests.filter((t) => t.status === 'failed').length,
0
);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

These pass/fail totals are read from testState.testCategories captured in the callback closure, so they can be stale at completion time (often showing 0/0 or previous-run counts). The same pattern appears again in runSCWReleaseTests's finally. Prefer deriving counts from a local accumulator during execution, or reading from a ref that is kept in sync with latest categories/results before emitting completion toasts.

Copy link
Copy Markdown
Collaborator

@fan-zhang-sv fan-zhang-sv left a comment

Choose a reason for hiding this comment

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

tested locally, both keys release and sdk release. Both worked and finished within a minute!

nit

  • seems like USDC on base sepolia is a pre-requisite, we can prob add this note somewhere to make this page more self explanatory
  • seems like we need to click Reset between each run (it makes sense!), we can prob add this a effect to the test control buttons after clicked

@spencerstock spencerstock merged commit 678c9ef into master Mar 26, 2026
9 checks passed
@spencerstock spencerstock deleted the spencer/e2e-playground branch March 26, 2026 18:14
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.

5 participants