en un clic
e2e-test-creation
// Create Cucumber BDD end-to-end tests for new features, enhancements, or updates. Use this when asked to write e2e tests, acceptance tests, feature files, step definitions, or Cucumber scenarios for Daedalus.
// Create Cucumber BDD end-to-end tests for new features, enhancements, or updates. Use this when asked to write e2e tests, acceptance tests, feature files, step definitions, or Cucumber scenarios for Daedalus.
Manage and maintain i18n messaging or copy for Daedalus multi-language support.
Add, update, validate and verify theme files using built-in package.json commands and utilities.
Create or update Storybook stories for Daedalus React components. Use this when asked to write stories, visual tests, storybook entries, or component demos for new or existing features.
Encode and decode bech32 strings using the local bech32 CLI.
Diagnose cardano-cli: version, era-prefixed vs legacy syntax, network flags. Produces compatibility report.
Plutus script guidance: datums, redeemers, collateral, reference scripts. Templates only—use operator to execute.
| name | e2e-test-creation |
| description | Create Cucumber BDD end-to-end tests for new features, enhancements, or updates. Use this when asked to write e2e tests, acceptance tests, feature files, step definitions, or Cucumber scenarios for Daedalus. |
Create Cucumber BDD end-to-end tests for the Daedalus wallet application. This skill covers writing .feature files, step definitions, and helpers following existing project conventions.
| Component | Technology |
|---|---|
| BDD Framework | Cucumber.js with Gherkin syntax |
| App Driver | Spectron (Electron testing) + WebDriver.io |
| Assertions | Chai (expect) |
| Mocking | Sinon (unit tests only) |
| TypeScript | Compiled via @swc-node/register |
| Test Runner | yarn test:e2e / yarn test:unit |
Tests are organized by domain under tests/. Each domain may have e2e/ and/or unit/ subtrees:
tests/{domain}/
├── e2e/
│ ├── features/
│ │ └── {feature-name}.feature # Gherkin scenarios
│ ├── steps/
│ │ ├── {feature-name}.ts # Step definitions
│ │ └── helpers.ts # Domain-specific helpers
│ └── documents/ # Test fixtures (optional)
└── unit/
├── features/
│ └── {feature-name}.feature
└── steps/
└── {feature-name}.ts
| Domain | Path | Description |
|---|---|---|
wallets | tests/wallets/ | Wallet CRUD, restore, ordering |
transactions | tests/transactions/ | Send, receive, UTxO display |
delegation | tests/delegation/ | Staking, stake pools, rewards |
navigation | tests/navigation/ | Sidebar, tabs, routing |
settings | tests/settings/ | Language, terms of use, preferences |
news | tests/news/ | Newsfeed, alerts, incidents |
paper-wallets | tests/paper-wallets/ | Paper wallet certificates |
addresses | tests/addresses/ | Address management |
app | tests/app/ | App lifecycle, updates, dialogs |
common | tests/common/ | Shared utilities, mnemonics |
assets | tests/assets/ | Native tokens, NFTs |
When adding tests for a new domain, create the full subtree. When adding tests for an existing domain, add files to the existing structure.
tests/{domain}/e2e/features/{feature-name}.feature
Every e2e feature file MUST start with a tag line. Use the appropriate tags:
| Tag | Meaning |
|---|---|
@e2e | Required — marks as an end-to-end test |
@unit | Marks as a unit test (use for unit/ features) |
@wip | Work in progress — skipped in CI |
@skip | Permanently skipped |
@watch | Run in watch mode during development |
@noReload | Skip app reload between scenarios |
@restartApp | Restart Electron app after this scenario |
@reconnectApp | Reconnect app backend after this scenario |
@rewardsCsv | Clean up exported rewards CSV after scenario |
@e2e
Feature: {Human-readable Feature Name}
Background:
Given I have completed the basic setup
Scenario: {Descriptive Scenario Name}
Given {precondition}
When {user action}
Then {expected outcome}
Background section: Use for shared preconditions across scenarios. Almost all e2e tests include Given I have completed the basic setup which handles initial app setup (language, terms of use acceptance).
Scenario Outline with Examples: Use for testing multiple data variations:
Scenario Outline: Switching Between Wallet Tabs
Given I am on the "Test wallet" wallet "<FROM>" screen
When I click the wallet <TO> button
Then I should be on the "Test wallet" wallet "<TO>" screen
Examples:
| FROM | TO |
| summary | send |
| summary | receive |
Data Tables: Use for structured test data:
Given I have the following wallets:
| name |
| Test wallet |
Quoted strings: Use double quotes for dynamic values referenced in step definitions:
Given I am on the "Test wallet" wallet "send" screen
These steps are already defined and available for use in feature files:
# Setup
Given I have completed the basic setup
Given I dont have a language set
Given I didn't accept "Terms of use"
# Wallet management
Given I have the following wallets:
| name |
| My Wallet |
Given I have a "{wallet}" wallet with funds
Given I am on the "{wallet}" wallet "{screen}" screen
# Navigation
When I click the wallet {button} button
When I click on the "{category}" category in the sidebar
# Sidebar
Given the sidebar submenu is hidden|visible
When I click on the sidebar toggle button
Then the sidebar submenu should be hidden|visible
# General assertions
Then I should be on the "{wallet}" wallet "{screen}" screen
tests/{domain}/e2e/steps/{feature-name}.ts
import { Given, When, Then } from 'cucumber';
import { expect } from 'chai';
// CSS selectors — group all selectors at the top
const SELECTORS = {
COMPONENT: '.MyComponent_component',
BUTTON: '.MyComponent_button',
DIALOG: '.MyDialog_overlay',
};
Given(/^I am on the my feature screen$/, async function () {
await this.client.waitForVisible(SELECTORS.COMPONENT);
});
When(/^I click the action button$/, async function () {
await this.waitAndClick(SELECTORS.BUTTON);
});
Then(/^I should see the dialog$/, async function () {
await this.client.waitForVisible(SELECTORS.DIALOG);
});
Then(/^I should not see the dialog$/, async function () {
await this.client.waitForVisible(SELECTORS.DIALOG, null, true);
});
Import pattern: Always import Given, When, Then from 'cucumber' and expect from 'chai'.
Regex matching: Use regex patterns for step matching. Capture dynamic values with groups:
Given(/^I have a "([^"]*)" wallet$/, async function (walletName) {
// walletName captured from quotes in feature file
});
Async functions: All step definitions MUST use async function (not arrow functions — Cucumber needs this context).
this context: The test context provides these properties:
this.client // WebDriver.io client — DOM interaction
this.app // Spectron Application instance
this.browserWindow // Electron BrowserWindow
this.context // Shared data object between steps
this.intl // i18n translation helper
this.waitAndClick(selector) // Wait for visible + enabled, then click
this.waitAndGetText(selector) // Wait for text, then get it
this.waitAndSetValue(selector, value) // Wait for element, then set value
CSS Selectors: Use CSS class selectors matching React component SCSS module classes. Daedalus uses SCSS modules, so class names follow {ComponentName}_{className} pattern:
'.WalletSendForm_component'
'.SidebarCategory_active'
'.TopBar_leftIcon'
'.NavButton_component.summary'
Selectors object: Group all selectors in a SELECTORS const at the top of the file for maintainability.
Executing code in the app context: Use this.client.execute() or this.client.executeAsync() to run code inside the Electron renderer process:
// Synchronous execution
const result = await this.client.execute(() => {
return daedalus.stores.wallets.active?.name;
});
// result.value contains the return value
// Async execution (with callback)
await this.client.executeAsync((done) => {
daedalus.stores.wallets.refreshWalletsData()
.then(done)
.catch((error) => done(error));
});
Accessing Daedalus internals: Inside execute/executeAsync, the global daedalus object exposes:
daedalus.stores // MobX stores (wallets, networkStatus, sidebar, profile, etc.)
daedalus.actions // MobX actions
daedalus.api // API client (daedalus.api.ada)
daedalus.translations // i18n translation objects
Assertions: Use Chai's expect for assertions:
const text = await this.waitAndGetText(SELECTORS.TITLE);
expect(text).to.equal('Expected Title');
expect(text).to.include('partial');
expect(result.value).to.be.true;
tests/{domain}/e2e/steps/helpers.ts
import { DEFAULT_TIMEOUT } from '../../../common/e2e/steps/config';
/**
* Wait for an element and extract data from it.
*/
export const getElementData = async (client, selector: string) => {
await client.waitForVisible(selector);
return client.getText(selector);
};
/**
* Wait until a condition is true in the app state.
*/
export const waitUntilCondition = async function (conditionFn: () => boolean) {
await this.client.waitUntil(
async () => {
const result = await this.client.execute(conditionFn);
return result.value === true;
},
DEFAULT_TIMEOUT
);
};
/**
* Navigate to a specific route in the app.
*/
export const navigateTo = async function (route: string) {
await this.client.execute((r) => {
daedalus.actions.router.goToRoute.trigger({ route: r });
}, route);
};
/**
* Wait until a specific URL is reached.
*/
export const waitUntilUrlEquals = async function (expectedUrl: string) {
const maxWait = DEFAULT_TIMEOUT;
const freq = 500;
let attempts = maxWait / freq;
const check = async () => {
const url = await this.client.url();
if (url.value.includes(expectedUrl)) return true;
if (--attempts <= 0) throw new Error(`URL never reached: ${expectedUrl}`);
await new Promise((r) => setTimeout(r, freq));
return check();
};
await check();
};
client as first parameter for functions that don't need this context.function (not arrow) for helpers that need this binding — they'll be called with .call(this, ...).DEFAULT_TIMEOUT from tests/common/e2e/steps/config.ts for consistent timeouts.tests/common/e2e/steps/helpers.ts| Helper | Purpose |
|---|---|
waitAndClick(selector) | Wait visible + enabled, then click |
waitAndGetText(selector) | Wait for text content, then retrieve it |
waitAndSetValue(sel, val) | Wait for element, then set its value |
scrollIntoView(client, sel) | Scroll element into viewport |
clickInputByLabel(label) | Click input field by its label text |
clickOptionByValue(value) | Click select option by value |
clickOptionByIndex(index) | Click select option by index |
saveScreenshot(app, file) | Capture screenshot (used on failure) |
skippablePromise(name, fn) | Wrap promise with skip support |
tests/common/e2e/steps/config.tsexport const DEFAULT_TIMEOUT = 20000;
tests/wallets/e2e/steps/helpers.ts| Helper | Purpose |
|---|---|
createWallets(wallets, options) | Create wallets via API |
waitUntilWalletIsLoaded(name) | Poll until wallet appears in sidebar |
restoreWalletWithFunds(client, opts) | Restore Shelley wallet with funds |
navigateTo(route) | Navigate to a specific app route |
waitUntilUrlEquals(url) | Wait until URL matches expected |
getWalletType(type) | Determine Shelley vs Byron wallet type |
Tag your new test with @watch for iterative development:
@e2e @watch
Feature: My New Feature
Run in watch mode:
KEEP_APP_AFTER_TESTS=true yarn test:e2e --tags '@e2e and @watch'
Run a single feature file:
yarn cucumber:run tests/{domain}/e2e/features/{feature-name}.feature
Run all e2e tests:
yarn test:e2e
Run with fail-fast (stop on first failure):
yarn test:e2e:fail-fast
Re-run only failed tests:
yarn test:e2e:rerun
@watch and @wip tags from finished tests@e2e (or @unit for unit tests)yarn test:e2e --tags '@e2e and @{your-feature-tag}'1. Feature file — tests/wallets/e2e/features/bookmark-wallet.feature:
@e2e
Feature: Bookmark Wallet
Background:
Given I have completed the basic setup
And I have the following wallets:
| name |
| Test wallet |
Scenario: User bookmarks a wallet
Given I am on the "Test wallet" wallet "summary" screen
When I click the bookmark button
Then the wallet should be marked as bookmarked
And I should see the bookmark indicator
Scenario: User removes a bookmark
Given I am on the "Test wallet" wallet "summary" screen
And the wallet is bookmarked
When I click the bookmark button
Then the wallet should not be marked as bookmarked
2. Step definitions — tests/wallets/e2e/steps/bookmark-wallet.ts:
import { Given, When, Then } from 'cucumber';
import { expect } from 'chai';
const SELECTORS = {
BOOKMARK_BUTTON: '.WalletSummary_bookmarkButton',
BOOKMARK_ACTIVE: '.WalletSummary_bookmarkActive',
BOOKMARK_INDICATOR: '.WalletSummary_bookmarkIndicator',
};
Given(/^the wallet is bookmarked$/, async function () {
const isBookmarked = await this.client.isVisible(SELECTORS.BOOKMARK_ACTIVE);
if (!isBookmarked) {
await this.waitAndClick(SELECTORS.BOOKMARK_BUTTON);
await this.client.waitForVisible(SELECTORS.BOOKMARK_ACTIVE);
}
});
When(/^I click the bookmark button$/, async function () {
await this.waitAndClick(SELECTORS.BOOKMARK_BUTTON);
});
Then(/^the wallet should be marked as bookmarked$/, async function () {
await this.client.waitForVisible(SELECTORS.BOOKMARK_ACTIVE);
});
Then(/^the wallet should not be marked as bookmarked$/, async function () {
await this.client.waitForVisible(SELECTORS.BOOKMARK_ACTIVE, null, true);
});
Then(/^I should see the bookmark indicator$/, async function () {
await this.client.waitForVisible(SELECTORS.BOOKMARK_INDICATOR);
});
For unit tests that don't require the full Electron app:
tests/{domain}/unit/features/{name}.feature@unit
Feature: {Feature Name}
Scenario: {Scenario Name}
Given {setup}
When {action}
Then {assertion}
tests/{domain}/unit/steps/{name}.tsimport { Given, When, Then } from 'cucumber';
import { expect } from 'chai';
import sinon from 'sinon';
Given('I have input data {string}', function (data) {
this.context.input = data;
});
When('I process the data', function () {
this.context.result = processData(this.context.input);
});
Then('the output should be {string}', function (expected) {
expect(this.context.result).to.equal(expected);
});
Unit test conventions:
@unit tag (not @e2e)this.context to share data between steps (set up in setup-common.ts)After hook)When creating e2e tests for a new feature, verify:
tests/{domain}/e2e/features/ with .feature extension@e2e tagGiven I have completed the basic setuptests/{domain}/e2e/steps/ with .ts extension{ Given, When, Then } from 'cucumber'async function (not arrow functions){ComponentName}_{className} SCSS module patternSELECTORS constanthelpers.ts file@watch / @wip tags are added during development and removed before mergeyarn test:e2e --tags '@e2e and @watch'