@aladas-org/cryptocalc
Version:
Cryptocurrency wallet generator
667 lines (622 loc) • 26.1 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>E2E Test Protocol — Dogecoin HD Wallet (Save & Open)</title>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Syne:wght@400;600;700;800&display=swap" rel="stylesheet" />
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0b0d12;
--surface: #111420;
--border: #1e2235;
--border-hi: #2e3450;
--text: #cdd3e8;
--muted: #5a6080;
--accent: #f7c948;
--accent2: #3d8ef7;
--accent3: #50e0a0;
--accent4: #e05080;
--pass: #50e0a0;
--fail: #e05080;
--mono: 'JetBrains Mono', monospace;
--sans: 'Syne', sans-serif;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--sans);
font-size: 15px;
line-height: 1.7;
padding: 0 0 80px;
}
/* ── HEADER ── */
header {
padding: 56px 64px 40px;
border-bottom: 1px solid var(--border);
position: relative;
overflow: hidden;
}
header::before {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 60% 80% at 10% 50%, #1a2040 0%, transparent 70%),
radial-gradient(ellipse 40% 60% at 90% 20%, #0d1a30 0%, transparent 70%);
z-index: 0;
}
header > * { position: relative; z-index: 1; }
.header-label {
font-family: var(--mono);
font-size: 11px;
letter-spacing: .18em;
color: var(--accent);
text-transform: uppercase;
margin-bottom: 16px;
}
h1 {
font-size: clamp(26px, 4vw, 40px);
font-weight: 800;
color: #eef0f8;
line-height: 1.15;
margin-bottom: 24px;
}
h1 span { color: var(--accent); }
.meta {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.meta-item {
font-family: var(--mono);
font-size: 12px;
background: var(--surface);
border: 1px solid var(--border-hi);
border-radius: 4px;
padding: 5px 12px;
color: var(--muted);
}
.meta-item strong { color: var(--text); }
/* ── LAYOUT ── */
main {
max-width: 1060px;
margin: 0 auto;
padding: 48px 64px 0;
}
/* ── SECTION TITLES ── */
.section-title {
font-family: var(--mono);
font-size: 10px;
letter-spacing: .2em;
color: var(--accent);
text-transform: uppercase;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
/* ── CONTEXT BOX ── */
.context-block {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 28px 32px;
margin-bottom: 48px;
}
.context-block p { color: var(--text); margin-bottom: 16px; }
.context-block p:last-child { margin-bottom: 0; }
/* ── CONSTRAINTS TABLE ── */
.constraint-grid {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
margin: 20px 0;
}
.constraint-row {
display: grid;
grid-template-columns: 1fr 1.6fr;
gap: 0;
border: 1px solid var(--border);
border-radius: 6px;
overflow: hidden;
font-size: 13px;
}
.constraint-key {
background: #141828;
padding: 10px 16px;
font-family: var(--mono);
color: var(--accent2);
border-right: 1px solid var(--border);
}
.constraint-val {
background: var(--surface);
padding: 10px 16px;
color: var(--muted);
}
/* ── HELPERS BLOCK ── */
.helpers-list {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 16px;
}
.helper-row {
display: flex;
align-items: baseline;
gap: 16px;
font-size: 13px;
}
.helper-name {
font-family: var(--mono);
color: var(--accent3);
min-width: 280px;
flex-shrink: 0;
}
.helper-desc { color: var(--muted); }
/* ── TEST CARDS ── */
.tests-grid {
display: flex;
flex-direction: column;
gap: 32px;
margin-bottom: 48px;
}
.test-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
overflow: hidden;
}
.test-card-header {
display: flex;
align-items: center;
gap: 20px;
padding: 20px 28px;
border-bottom: 1px solid var(--border);
background: #0f1220;
}
.test-number {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--mono);
font-size: 13px;
font-weight: 700;
flex-shrink: 0;
background: #1a2a3a;
color: var(--accent2);
border: 1px solid #2a4060;
}
.test-title-wrap { flex: 1; }
.test-id {
font-family: var(--mono);
font-size: 10px;
color: var(--muted);
letter-spacing: .12em;
text-transform: uppercase;
margin-bottom: 4px;
}
.test-title {
font-size: 15px;
font-weight: 700;
color: #eef0f8;
}
.test-title code {
font-family: var(--mono);
font-size: 13px;
color: var(--accent);
background: #1a1c28;
padding: 1px 6px;
border-radius: 3px;
}
.test-body {
padding: 24px 28px;
display: flex;
flex-direction: column;
gap: 24px;
}
/* sub-sections */
.sub-label {
font-family: var(--mono);
font-size: 10px;
letter-spacing: .15em;
color: var(--muted);
text-transform: uppercase;
margin-bottom: 10px;
}
.objective-text { color: var(--text); font-size: 14px; line-height: 1.65; }
/* preconditions */
.precond-list {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.precond-chip {
font-family: var(--mono);
font-size: 12px;
padding: 4px 12px;
border-radius: 20px;
background: #141828;
border: 1px solid var(--border-hi);
color: var(--text);
}
/* steps table */
.steps-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.steps-table th {
font-family: var(--mono);
font-size: 10px;
letter-spacing: .12em;
color: var(--muted);
text-transform: uppercase;
text-align: left;
padding: 8px 14px;
background: #0f1220;
border-bottom: 1px solid var(--border);
}
.steps-table td {
padding: 9px 14px;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
.steps-table tr:last-child td { border-bottom: none; }
.steps-table tr:hover td { background: #141828; }
.step-num {
font-family: var(--mono);
color: var(--muted);
width: 30px;
}
.step-action { color: var(--text); }
.step-method {
font-family: var(--mono);
font-size: 12px;
color: var(--accent3);
}
/* assertion box */
.assertion-box {
background: #0f1220;
border: 1px solid var(--border-hi);
border-left: 3px solid var(--accent3);
border-radius: 4px;
padding: 14px 18px;
font-family: var(--mono);
font-size: 13px;
color: var(--accent3);
white-space: pre;
}
.assertion-box.regex { border-left-color: var(--accent2); color: var(--accent2); }
.assertion-box.ineq { border-left-color: var(--accent2); color: var(--accent2); }
.assertion-box.eq { border-left-color: var(--accent4); color: var(--accent4); }
/* failure list */
.failure-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
}
.failure-list li {
display: flex;
gap: 12px;
font-size: 13px;
align-items: baseline;
}
.fail-bullet {
font-family: var(--mono);
color: var(--fail);
flex-shrink: 0;
}
.fail-text { color: var(--muted); }
.fail-text strong { color: var(--text); font-weight: 600; }
/* design note */
.design-note {
background: #130d20;
border: 1px solid #2a1a40;
border-left: 3px solid #a080ff;
border-radius: 4px;
padding: 14px 18px;
font-size: 13px;
color: #9070cc;
line-height: 1.65;
}
.design-note strong { color: #c0a0ff; }
.design-note code {
font-family: var(--mono);
font-size: 12px;
background: #1a1030;
padding: 1px 5px;
border-radius: 3px;
color: var(--accent4);
}
/* ── SUMMARY TABLE ── */
.summary-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.summary-table th {
font-family: var(--mono);
font-size: 10px;
letter-spacing: .12em;
color: var(--muted);
text-transform: uppercase;
text-align: left;
padding: 10px 16px;
background: #0f1220;
border-bottom: 1px solid var(--border);
}
.summary-table td {
padding: 11px 16px;
border-bottom: 1px solid var(--border);
color: var(--text);
}
.summary-table tr:last-child td { border-bottom: none; }
.summary-table tr:hover td { background: #141828; }
.badge {
font-family: var(--mono);
font-size: 11px;
padding: 3px 10px;
border-radius: 12px;
display: inline-block;
}
.badge-regex { background: #0f2030; color: var(--accent2); border: 1px solid #1a4060; }
.badge-ineq { background: #101a10; color: var(--accent3); border: 1px solid #1a3020; }
.badge-eq { background: #1a0820; color: var(--accent4); border: 1px solid #300a30; }
.badge-doge { background: #201808; color: var(--accent); border: 1px solid #403010; }
/* ── CODE INLINE ── */
code {
font-family: var(--mono);
font-size: 12.5px;
color: var(--accent4);
background: #1a1028;
padding: 1px 6px;
border-radius: 3px;
}
/* ── PHASE CHIPS ── */
.phase-tag {
display: inline-block;
font-family: var(--mono);
font-size: 10px;
padding: 2px 8px;
border-radius: 12px;
background: #1a1c28;
border: 1px solid var(--border-hi);
color: var(--accent2);
margin-right: 8px;
}
/* ── SCROLLBAR ── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--border-hi); border-radius: 3px; }
</style>
</head>
<body>
<header>
<div class="header-label">Playwright · Electron E2E</div>
<h1>E2E Test Protocol<br><span>Dogecoin HD Wallet (Save & Open)</span></h1>
<div class="meta">
<div class="meta-item"><strong>File</strong> tests/playwright/e2e/dogecoin_hd_wallet_usecase.test.js</div>
<div class="meta-item"><strong>Suite</strong> Use Case - Dogecoin HD Wallet : generate, save, open, edit, save</div>
<div class="meta-item"><strong>Tests</strong> 1 (complete 6-phase scenario)</div>
<div class="meta-item"><strong>Blockchain</strong> Dogecoin</div>
</div>
</header>
<main>
<!-- ── GENERAL CONTEXT ── -->
<div class="section-title">General Context</div>
<div class="context-block">
<p>
This test drives the Cryptocalc application as a real user would, through its Electron GUI.
It launches a fresh instance of the application, performs UI interactions, and verifies
the resulting state of wallet fields as well as files saved in the <code>_output/</code> folder.
</p>
<div class="sub-label">Confirmed Application Behavior</div>
<div class="constraint-grid">
<div class="constraint-row">
<div class="constraint-key">Save</div>
<div class="constraint-val">No file-picker. Direct write to <code>_output/<timestamp>_<COIN>_<LANG>/</code> followed by an iziToast with "Show" and "Close" buttons</div>
</div>
<div class="constraint-row">
<div class="constraint-key">Open</div>
<div class="constraint-val">Native OS dialog to choose the file — mocked via <code>electronApp.evaluate({ dialog })</code></div>
</div>
</div>
<div class="sub-label" style="margin-top:20px;">Key Technical Constraints</div>
<div class="constraint-grid">
<div class="constraint-row">
<div class="constraint-key">#account_id, #address_index_id</div>
<div class="constraint-val">Read-only fields — modified via <code>page.evaluate()</code> to bypass Playwright's refusal to fill readonly inputs</div>
</div>
<div class="constraint-row">
<div class="constraint-key">#refresh_btn_id, #save_icon_id, #file_open_icon_id</div>
<div class="constraint-val">May be <code>disabled</code> — clicked via <code>evaluate()</code> after removing the <code>disabled</code> attribute</div>
</div>
<div class="constraint-row">
<div class="constraint-key">Address generation</div>
<div class="constraint-val">Asynchronous — 5s pause after each <code>clickRefresh()</code></div>
</div>
<div class="constraint-row">
<div class="constraint-key">Save operation</div>
<div class="constraint-val">5s wait after iziToast appears to allow file writes to complete</div>
</div>
<div class="constraint-row">
<div class="constraint-key">_output/ directory</div>
<div class="constraint-val">Cleaned before each test to prevent interference</div>
</div>
</div>
<div class="sub-label" style="margin-top:20px;">Shared Helpers</div>
<div class="helpers-list">
<div class="helper-row"><span class="helper-name">switchToHDWallet(page)</span><span class="helper-desc">Navigate to Wallet tab, select "HD Wallet" mode</span></div>
<div class="helper-row"><span class="helper-name">selectBlockchain(page, blockchain)</span><span class="helper-desc">Select a blockchain from the dynamic dropdown</span></div>
<div class="helper-row"><span class="helper-name">setFieldValue(page, id, val)</span><span class="helper-desc">Inject a value into any field (readonly-safe), dispatches input + change</span></div>
<div class="helper-row"><span class="helper-name">clickRefresh(page)</span><span class="helper-desc">Trigger refresh via evaluate() — generates a new address</span></div>
<div class="helper-row"><span class="helper-name">getDisplayedAddress(page)</span><span class="helper-desc">Read the current text content of <code>#address_id</code></span></div>
<div class="helper-row"><span class="helper-name">saveWallet(page)</span><span class="helper-desc">Click Save, wait for iziToast, dismiss it, and return the path to the created .wits file</span></div>
<div class="helper-row"><span class="helper-name">openWallet(page, electronApp, filePath)</span><span class="helper-desc">Mock the open dialog and click the open icon</span></div>
<div class="helper-row"><span class="helper-name">logWalletState(page, label)</span><span class="helper-desc">Log the state of main fields for debugging</span></div>
<div class="helper-row"><span class="helper-name">cleanupOutputDir()</span><span class="helper-desc">Delete all folders and files in <code>_output/</code></span></div>
</div>
</div>
<!-- ── TEST ── -->
<div class="section-title">Test Protocol</div>
<div class="tests-grid">
<!-- SINGLE TEST (COMPLETE SCENARIO) -->
<div class="test-card">
<div class="test-card-header">
<div class="test-number">01</div>
<div class="test-title-wrap">
<div class="test-id">Complete Workflow</div>
<div class="test-title">Generate, save, open, and modify a Dogecoin HD wallet</div>
</div>
</div>
<div class="test-body">
<div>
<div class="sub-label">Objective</div>
<p class="objective-text">
Verify the complete lifecycle of a Dogecoin HD wallet:
<span class="phase-tag">Phase 1</span> Initial setup (account=1, index=3)<br>
<span class="phase-tag">Phase 2</span> Generation + address format validation<br>
<span class="phase-tag">Phase 3</span> Save → verify JSON file in _output/<br>
<span class="phase-tag">Phase 4</span> Reopen file → verify reload<br>
<span class="phase-tag">Phase 5</span> Modify (account=4, index=7) + refresh<br>
<span class="phase-tag">Phase 6</span> Second save → verify update
</p>
</div>
<div>
<div class="sub-label">Preconditions</div>
<div class="precond-list">
<span class="precond-chip">App launched & ready</span>
<span class="precond-chip">Wallet tab active</span>
<span class="precond-chip">Mode: HD Wallet</span>
<span class="precond-chip">Blockchain: Dogecoin</span>
<span class="precond-chip">_output/ folder empty</span>
</div>
</div>
<div>
<div class="sub-label">Detailed Phases</div>
<!-- Phase 1 & 2 -->
<table class="steps-table" style="margin-bottom:16px;">
<thead><tr><th>Phase</th><th>Action</th><th>Method</th></tr></thead>
<tbody>
<tr><td class="step-num">1</td><td class="step-action">Set account=1, index=3</td><td class="step-method">setFieldValue(page, 'account_id', '1')<br>setFieldValue(page, 'address_index_id', '3')</td></tr>
<tr><td class="step-num">2</td><td class="step-action">Trigger address generation</td><td class="step-method">clickRefresh(page)</td></tr>
<tr><td class="step-num">2b</td><td class="step-action">Read generated address</td><td class="step-method">getDisplayedAddress(page) → initialAddress</td></tr>
</tbody>
</table>
<!-- Phase 3 -->
<table class="steps-table" style="margin-bottom:16px;">
<thead><tr><th>Phase</th><th>Action</th><th>Method</th></tr></thead>
<tbody>
<tr><td class="step-num">3</td><td class="step-action">Save wallet</td><td class="step-method">saveWallet(page) → savedPath1</td></tr>
<tr><td class="step-num">3b</td><td class="step-action">Verify file exists</td><td class="step-method">fs.existsSync(savedPath1) === true</td></tr>
<tr><td class="step-num">3c</td><td class="step-action">Verify JSON contains address</td><td class="step-method">JSON.parse() .includes(initialAddress)</td></tr>
</tbody>
</table>
<!-- Phase 4 -->
<table class="steps-table" style="margin-bottom:16px;">
<thead><tr><th>Phase</th><th>Action</th><th>Method</th></tr></thead>
<tbody>
<tr><td class="step-num">4</td><td class="step-action">Open saved file</td><td class="step-method">openWallet(page, electronApp, savedPath1)</td></tr>
<tr><td class="step-num">4b</td><td class="step-action">Read reloaded address</td><td class="step-method">getDisplayedAddress(page) → reloadedAddress</td></tr>
</tbody>
</table>
<!-- Phase 5 -->
<table class="steps-table" style="margin-bottom:16px;">
<thead><tr><th>Phase</th><th>Action</th><th>Method</th></tr></thead>
<tbody>
<tr><td class="step-num">5</td><td class="step-action">Modify account=4, index=7</td><td class="step-method">setFieldValue(page, 'account_id', '4')<br>setFieldValue(page, 'address_index_id', '7')</td></tr>
<tr><td class="step-num">5b</td><td class="step-action">Refresh</td><td class="step-method">clickRefresh(page)</td></tr>
<tr><td class="step-num">5c</td><td class="step-action">Read new address</td><td class="step-method">getDisplayedAddress(page) → updatedAddress</td></tr>
</tbody>
</table>
<!-- Phase 6 -->
<table class="steps-table">
<thead><tr><th>Phase</th><th>Action</th><th>Method</th></tr></thead>
<tbody>
<tr><td class="step-num">6</td><td class="step-action">Save modified wallet</td><td class="step-method">saveWallet(page) → savedPath2</td></tr>
<tr><td class="step-num">6b</td><td class="step-action">Verify second file exists</td><td class="step-method">fs.existsSync(savedPath2) === true</td></tr>
<tr><td class="step-num">6c</td><td class="step-action">Verify JSON contains new address</td><td class="step-method">JSON.parse() .includes(updatedAddress)</td></tr>
<tr><td class="step-num">6d</td><td class="step-action">Verify old address is not present</td><td class="step-method">! .includes(initialAddress)</td></tr>
</tbody>
</table>
</div>
<div>
<div class="sub-label">Assertions</div>
<div class="assertion-box regex">/^D[1-9A-HJ-NP-Za-km-z]{32,34}$/ ← valid Dogecoin address format</div>
<div class="assertion-box ineq" style="margin-top:10px;">updatedAddress ≠ initialAddress</div>
<div class="assertion-box eq" style="margin-top:10px;">reloadedAddress = initialAddress</div>
</div>
<div>
<div class="sub-label">Failure Scenarios</div>
<ul class="failure-list">
<li><span class="fail-bullet">✗</span><span class="fail-text"><strong>Empty address</strong> — generation did not complete within timeout</span></li>
<li><span class="fail-bullet">✗</span><span class="fail-text"><strong>Invalid address format</strong> — regex not satisfied (wrong blockchain or derivation error)</span></li>
<li><span class="fail-bullet">✗</span><span class="fail-text"><strong>No file created</strong> — save failed (incorrect path or write error)</span></li>
<li><span class="fail-bullet">✗</span><span class="fail-text"><strong>reloadedAddress ≠ initialAddress</strong> — open did not properly reload the wallet</span></li>
<li><span class="fail-bullet">✗</span><span class="fail-text"><strong>updatedAddress = initialAddress</strong> — parameter changes were not applied</span></li>
<li><span class="fail-bullet">✗</span><span class="fail-text"><strong>Second file contains old address</strong> — update was not saved</span></li>
</ul>
</div>
<div>
<div class="sub-label">Implementation Note</div>
<div class="design-note">
<strong>Open dialog mock:</strong> Attempting to mock via <code>electronApp.evaluate()</code> may fail with
<code>ReferenceError: require is not defined</code>. The test is designed to continue even if the mock fails —
in that case, the real system dialog would appear, but in CI/headless environments, the application typically
uses a default path or the test is configured to run without user interaction.<br><br>
<strong>Cleanup:</strong> The <code>_output/</code> folder is completely cleaned before each test to ensure
no residual files from previous runs interfere.
</div>
</div>
</div>
</div>
</div><!-- /tests-grid -->
<!-- ── SUMMARY ── -->
<div class="section-title">Summary</div>
<div class="context-block" style="padding:0;overflow:hidden;">
<table class="summary-table">
<thead>
<tr><th>Phase</th><th>Action</th><th>Assertion</th><th>Blockchain</th></tr>
</thead>
<tbody>
<tr>
<td style="font-family:var(--mono);color:var(--muted);">1-2</td>
<td>Initial generation (account=1, index=3)</td>
<td><span class="badge badge-regex">regex</span></td>
<td><span class="badge badge-doge">Dogecoin</span></td>
</tr>
<tr>
<td style="font-family:var(--mono);color:var(--muted);">3</td>
<td>First save</td>
<td><span class="badge badge-eq">file created + contains address</span></td>
<td><span class="badge badge-doge">Dogecoin</span></td>
</tr>
<tr>
<td style="font-family:var(--mono);color:var(--muted);">4</td>
<td>Reopen file</td>
<td><span class="badge badge-eq">reloaded address = initial address</span></td>
<td><span class="badge badge-doge">Dogecoin</span></td>
</tr>
<tr>
<td style="font-family:var(--mono);color:var(--muted);">5</td>
<td>Modify (account=4, index=7)</td>
<td><span class="badge badge-ineq">new address ≠ old address</span></td>
<td><span class="badge badge-doge">Dogecoin</span></td>
</tr>
<tr>
<td style="font-family:var(--mono);color:var(--muted);">6</td>
<td>Second save</td>
<td><span class="badge badge-eq">second file created + contains new address</span></td>
<td><span class="badge badge-doge">Dogecoin</span></td>
</tr>
</tbody>
</table>
</div>
</main>
</body>
</html>