## Summary This PR makes URL serialization logic available to Node.js contexts (like Cypress tests) and replaces a hard-coded 4812-character base64 URL in tests with programmatically generated state. This builds on the shared utilities refactoring from #8246. ### Changes #### 1. Extract URL Serialization to Shared Module **Problem:** URL serialization code depended on GoldenLayout's browser-only ConfigMinifier, preventing Cypress spec files from importing it (they load in Node.js before running in browser). **Solution:** Created `shared/url-serialization.ts` with a Node-compatible ConfigMinifier reimplementation. **Technical Details:** - Reimplemented GoldenLayout's ConfigMinifier without browser dependencies - Moved serialization functions (`serialiseState`, `deserialiseState`, `risonify`, `unrisonify`) to shared module - Moved minification functions (`minifyConfig`, `unminifyConfig`) to shared module - Updated `static/url.ts` to use shared module instead of GoldenLayout - Added comprehensive test coverage in `test/url-serialization.ts` **Files:** - **New:** `shared/url-serialization.ts` (~279 lines) - **Modified:** `static/url.ts` (removed ~30 lines, eliminated GoldenLayout dependency) - **New:** `test/url-serialization.ts` (~96 lines) #### 2. Replace Hard-coded Cypress URL with Programmatic State **Before:** A hard-coded 4812-character base64 URL containing state for all panes ```typescript cy.visit('http://localhost:10240/#z:OYLghAFBqd5TB8IAsQGMD2ATApgUWwEsAXTAJwBoiQIAzIgG...'); ``` **After:** Programmatically generated state using `buildKnownGoodState()` function ```typescript const state = buildKnownGoodState(); const hash = serialiseState(state); cy.visit(`http://localhost:10240/#${hash}`, {...}); ``` **Benefits:** - Human-readable, maintainable test state - Programmatic generation from `PANE_DATA_MAP` keys - Layout optimized with 8 panes per row - Produces identical compressed URL format - Much easier to add/modify panes in the future #### 3. PANE_DATA_MAP Consistency Improvements Updated `PANE_DATA_MAP` to use component names exactly as registered with GoldenLayout: **Key renames:** - `preprocessor` → `pp` - `llvmir` → `ir` - `pipeline` → `llvmOptPipelineView` - `mir` → `rustmir` - `hir` → `rusthir` - `macro` → `rustmacroexp` - `core` → `haskellCore` - `stg` → `haskellStg` - `cmm` → `haskellCmm` - `dump` → `gccdump` - `tree` → `gnatdebugtree` - `debug` → `gnatdebug` **Added panes:** `codeEditor`, `compiler`, `conformance`, `output` (were missing from map) **Re-enabled tests:** - `yul` pane test (was commented out, now fixed) - `clojuremacroexp` pane test (was commented out, now fixed) - `cfg` pane test (had TODO, now removed) **Why this matters:** The `buildKnownGoodState()` function uses `Object.keys(PANE_DATA_MAP)` as the `componentName` property, so keys must match the actual registered component names for GoldenLayout to find them. ## Test Plan - [x] All Cypress tests pass (confirmed by @mattgodbolt) - [x] TypeScript compilation passes (`npm run ts-check`) - [x] Linting passes (`npm run lint`) - [x] URL serialization tests pass (3/3 tests) - [x] Pre-commit hooks pass - [x] Related vitest tests pass ## Dependencies - Builds on #8246 (shared utilities refactoring - already merged) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
Cypress E2E Tests
This directory contains end-to-end tests for Compiler Explorer using Cypress.
Running Tests
Starting Compiler Explorer for Testing
First, start a local Compiler Explorer instance with a clean configuration:
npm run dev -- --language c++ --no-local
The --no-local flag is important as it ensures your setup is clean of any local properties.
Running Cypress Tests
In another terminal:
# Run all Cypress tests
npm run cypress
# Run specific test file
npm run cypress -- run --spec "cypress/e2e/claude-explain.cy.ts"
# Open Cypress interactive UI (recommended for development)
npm run cypress:open
When using the interactive UI, choose "E2E Testing" and select your browser.
Important Testing Patterns & Lessons Learned
1. Always Use :visible Selectors
GoldenLayout creates template elements that exist in the DOM but aren't visible. Always use :visible to avoid selecting template elements:
// ❌ Bad - might select template elements
cy.get('.explain-content').should('contain', 'text');
// ✅ Good - only selects visible elements
cy.get('.explain-content:visible').should('contain', 'text');
2. Performance: Clear Intercepts in afterEach
Cypress intercepts accumulate across tests causing O(n²) performance degradation. Always clear them:
import {clearAllIntercepts} from '../support/utils';
afterEach(() => {
// Use the utility function to clear intercepts
clearAllIntercepts();
// Or manually clear them:
cy.state('routes', []);
cy.state('aliases', {});
// ... other cleanup
});
3. Mock Setup Timing is Critical
Always set up API mocks BEFORE any action that might trigger requests:
// ❌ Bad - pane constructor might make requests before mocks are ready
openClaudeExplainPane();
mockClaudeExplainAPI();
// ✅ Good - mocks ready before pane opens
mockClaudeExplainAPI();
openClaudeExplainPane();
4. Wait for Async DOM Updates
Don't just wait for API calls - wait for the actual DOM changes:
// ❌ Bad - API completes but DOM might not be updated yet
cy.wait('@getOptions');
cy.get('.dropdown').select('value');
// ✅ Good - wait for specific DOM state
cy.wait('@getOptions');
cy.get('.dropdown option[value="loading"]').should('not.exist');
cy.get('.dropdown').select('value');
5. Use Test Data, Not Production Values
Always use clearly fake test data to:
- Prevent confusion with real values
- Make it obvious when viewing test output
- Ensure tests never accidentally hit production APIs
// Use values like: test_first, test_second, focus_a, focus_b
// Not: beginner, expert, assembly, optimization
6. Helper Functions for Common Patterns
Extract common test patterns to helpers, but keep them in the test file if they're specific to one feature:
// In test file for feature-specific helpers
function openClaudeExplainPaneWithOptions() {
mockClaudeExplainAPI();
openClaudeExplainPane();
cy.wait('@getOptions');
waitForDropdownsToLoad();
}
// In utils.ts for general helpers
export function setMonacoEditorContent(content: string) { ... }
7. State Persistence Between Pane Instances
Be aware that some state might be static (shared between instances) while other state is per-instance:
- Static state (like consent, cache) persists when closing/reopening panes
- Instance state is lost when panes close
- This affects how you structure tests for features that should persist
8. Block Production APIs
Always block production API calls in tests to catch configuration issues:
cy.intercept('https://api.compiler-explorer.com/**', {
statusCode: 500,
body: {error: 'BLOCKED PRODUCTION API'}
}).as('blockedProduction');
Common Issues & Solutions
Tests Getting Progressively Slower
- Cause: Intercept accumulation
- Solution: Clear intercepts in
afterEachusingclearAllIntercepts()from utils or manually withcy.state('routes', [])
"Element not found" Despite Being Visible
- Cause: Selecting template elements from GoldenLayout
- Solution: Use
:visiblepseudo-selector
API Mocks Not Working
- Cause: Mock setup after the request is made
- Solution: Set up mocks before opening panes or triggering actions
Dropdown Selection Failing
- Cause: Trying to select before async population completes
- Solution: Wait for loading indicators to disappear first
State Not Persisting in Tests
- Cause: Not understanding static vs instance variables
- Solution: Check if the feature uses static state that should persist
Test Organization
- Keep feature-specific test helpers in the test file itself
- Only put truly reusable utilities in
support/utils.ts - Use descriptive helper function names that indicate what they do
- Group related tests in
describeblocks - Use consistent test data across all tests in a feature
Debugging Tips
- Use
cy.log()to debug what values you're actually getting - Check the Cypress command log for unexpected API calls
- Look for console errors that might indicate JavaScript issues
- Use
.then()to inspect element state at specific points - Check network tab for requests hitting production instead of mocks