refactor: shorten steps by using dedicated step, and test underlying micro steps

This commit is contained in:
linonetwo 2025-12-06 01:41:22 +08:00
parent dda10b25fb
commit ed01ba8036
5 changed files with 183 additions and 119 deletions

View file

@ -25,12 +25,9 @@ Feature: Filesystem Plugin
"""
# Wait for watch-fs to detect and add the tiddler
Then I wait for tiddler "WatchTestTiddler" to be added by watch-fs
# Open sidebar "最近" tab to see the timeline
And I click on "sidebar tab" element in browser view with selector "div.tc-tab-buttons.tc-sidebar-tabs-main > button:has-text('')"
# wait for tw animation, sidebar need time to show
And I wait for 1 seconds
# Click on the tiddler link in timeline to open it
And I click on "timeline link" element in browser view with selector "div.tc-timeline a.tc-tiddlylink:has-text('WatchTestTiddler')"
# Open the tiddler directly
When I open tiddler "WatchTestTiddler" in browser view
And I wait for 0.5 seconds
# Verify the tiddler content is displayed
Then I should see "Initial content from filesystem" in the browser view content
@ -46,10 +43,8 @@ Feature: Filesystem Plugin
Original content
"""
Then I wait for tiddler "TestTiddler" to be added by watch-fs
# Open the tiddler to view it
And I click on "sidebar tab" element in browser view with selector "div.tc-tab-buttons.tc-sidebar-tabs-main > button:has-text('')"
And I wait for 0.5 seconds
And I click on "timeline link" element in browser view with selector "div.tc-timeline a.tc-tiddlylink:has-text('TestTiddler')"
# Open the tiddler directly
When I open tiddler "TestTiddler" in browser view
And I wait for 0.5 seconds
Then I should see "Original content" in the browser view content
# Modify the file externally
@ -61,10 +56,9 @@ Feature: Filesystem Plugin
# Now delete the file externally
When I delete file "{tmpDir}/wiki/tiddlers/TestTiddler.tid"
Then I wait for tiddler "TestTiddler" to be deleted by watch-fs
# Re-open timeline to see updated list
And I click on "sidebar tab" element in browser view with selector "div.tc-tab-buttons.tc-sidebar-tabs-main > button:has-text('')"
# The timeline should not have a clickable link to TestTiddler anymore
Then I should not see a "TestTiddler timeline link" element in browser view with selector "div.tc-timeline a.tc-tiddlylink:has-text('TestTiddler')"
And I wait for 0.5 seconds
# The tiddler should show missing message
Then I should see "" in the browser view content
@file-watching
Scenario: Deleting open tiddler file shows missing tiddler message
@ -87,10 +81,9 @@ Feature: Filesystem Plugin
Content before rename
"""
Then I wait for tiddler "OldName" to be added by watch-fs
# Open sidebar to see the timeline
And I click on "sidebar tab" element in browser view with selector "div.tc-tab-buttons.tc-sidebar-tabs-main > button:has-text('')"
# Open the tiddler directly
When I open tiddler "OldName" in browser view
And I wait for 0.5 seconds
And I click on "timeline link" element in browser view with selector "div.tc-timeline a.tc-tiddlylink:has-text('OldName')"
Then I should see "Content before rename" in the browser view content
# Rename the file externally
When I rename file "{tmpDir}/wiki/tiddlers/OldName.tid" to "{tmpDir}/wiki/tiddlers/NewName.tid"
@ -105,11 +98,9 @@ Feature: Filesystem Plugin
"""
# Wait for the new tiddler to be detected and synced
Then I wait for tiddler "NewName" to be updated by watch-fs
# Navigate to timeline to verify changes
And I click on "sidebar tab" element in browser view with selector "div.tc-tab-buttons.tc-sidebar-tabs-main > button:has-text('')"
And I wait for 1 seconds
# Verify new name appears
And I click on "timeline link" element in browser view with selector "div.tc-timeline a.tc-tiddlylink:has-text('NewName')"
# Open the renamed tiddler directly
When I open tiddler "NewName" in browser view
And I wait for 0.5 seconds
Then I should see "Content before rename" in the browser view content
@file-watching
@ -117,11 +108,9 @@ Feature: Filesystem Plugin
# Modify an existing tiddler file by adding a tags field to TiddlyWikiIconBlue.png
When I modify file "{tmpDir}/wiki/tiddlers/TiddlyWikiIconBlue.png.tid" to add field "tags: TestTag"
Then I wait for tiddler "TiddlyWikiIconBlue.png" to be updated by watch-fs
# Open the tiddler to verify the tag was added
And I click on "sidebar tab" element in browser view with selector "div.tc-tab-buttons.tc-sidebar-tabs-main > button:has-text('')"
And I wait for 1 seconds
And I click on "timeline link" element in browser view with selector "div.tc-timeline a.tc-tiddlylink:has-text('TiddlyWikiIconBlue.png')"
And I wait for 1 seconds
# Open the tiddler directly to verify the tag was added
When I open tiddler "TiddlyWikiIconBlue.png" in browser view
And I wait for 0.5 seconds
# Verify the tag appears in the tiddler using data attribute
Then I should see a "TestTag tag" element in browser view with selector "[data-tiddler-title='TiddlyWikiIconBlue.png'] [data-tag-title='TestTag']"
# Now modify Index.tid by adding a tags field
@ -133,10 +122,8 @@ Feature: Filesystem Plugin
# Modify favicon.ico.meta file by adding a tags field
When I modify file "{tmpDir}/wiki/tiddlers/favicon.ico.meta" to add field "tags: IconTag"
Then I wait for tiddler "favicon.ico" to be updated by watch-fs
# Navigate to favicon.ico tiddler
And I click on "sidebar tab" element in browser view with selector "div.tc-tab-buttons.tc-sidebar-tabs-main > button:has-text('')"
# Open the favicon.ico tiddler directly
When I open tiddler "favicon.ico" in browser view
And I wait for 0.5 seconds
And I click on "timeline link" element in browser view with selector "div.tc-timeline a.tc-tiddlylink[href='#favicon.ico']"
And I wait for 1 seconds
# Verify the IconTag appears in favicon.ico tiddler
Then I should see a "IconTag tag" element in browser view with selector "[data-tiddler-title='favicon.ico'] [data-tag-title='IconTag']"

View file

@ -1,6 +1,16 @@
import { Then, When } from '@cucumber/cucumber';
import { backOff } from 'exponential-backoff';
import { clickElement, clickElementWithText, elementExists, executeTiddlyWikiCode, getDOMContent, getTextContent, isLoaded, pressKey, typeText } from '../supports/webContentsViewHelper';
import {
clickElement,
clickElementWithText,
elementExists,
executeTiddlyWikiCode,
getDOMContent,
getTextContent,
isLoaded,
pressKey,
typeText,
} from '../supports/webContentsViewHelper';
import type { ApplicationWorld } from './application';
// Backoff configuration for retries
@ -226,3 +236,101 @@ When('I open tiddler {string} in browser view', async function(this: Application
throw new Error(`Failed to open tiddler "${tiddlerTitle}" in browser view: ${error as Error}`);
}
});
/**
* Create a new tiddler with title and optional tags via TiddlyWiki UI.
* This step handles all the UI interactions: click add button, set title, add tags, and confirm.
*/
When('I create a tiddler {string} with tag {string} in browser view', { timeout: 20000 }, async function(
this: ApplicationWorld,
tiddlerTitle: string,
tagName: string,
) {
if (!this.app) {
throw new Error('Application not launched');
}
// Click add tiddler button
await clickElement(this.app, 'button:has(.tc-image-new-button)');
await new Promise(resolve => setTimeout(resolve, 300));
// Click on title input
await clickElement(this.app, "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor");
await new Promise(resolve => setTimeout(resolve, 200));
// Select all and delete to clear the default title
await pressKey(this.app, 'Control+a');
await new Promise(resolve => setTimeout(resolve, 100));
await pressKey(this.app, 'Delete');
await new Promise(resolve => setTimeout(resolve, 100));
// Type the tiddler title
await typeText(this.app, "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor", tiddlerTitle);
await new Promise(resolve => setTimeout(resolve, 500));
// Click on tag input
await clickElement(this.app, "div[data-tiddler-title^='Draft of'] div.tc-edit-add-tag-ui input.tc-edit-texteditor[placeholder='标签名称']");
await new Promise(resolve => setTimeout(resolve, 200));
// Type the tag name
await typeText(this.app, "div[data-tiddler-title^='Draft of'] div.tc-edit-add-tag-ui input.tc-edit-texteditor[placeholder='标签名称']", tagName);
await new Promise(resolve => setTimeout(resolve, 200));
// Click add tag button
await clickElement(this.app, "div[data-tiddler-title^='Draft of'] span.tc-add-tag-button button");
await new Promise(resolve => setTimeout(resolve, 300));
// Click confirm button to save
await clickElement(this.app, 'button:has(.tc-image-done-button)');
await new Promise(resolve => setTimeout(resolve, 500));
});
/**
* Create a new tiddler with title and custom field via TiddlyWiki UI.
*/
When('I create a tiddler {string} with field {string} set to {string} in browser view', { timeout: 20000 }, async function(
this: ApplicationWorld,
tiddlerTitle: string,
fieldName: string,
fieldValue: string,
) {
if (!this.app) {
throw new Error('Application not launched');
}
// Click add tiddler button
await clickElement(this.app, 'button:has(.tc-image-new-button)');
await new Promise(resolve => setTimeout(resolve, 300));
// Click on title input
await clickElement(this.app, "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor");
await new Promise(resolve => setTimeout(resolve, 200));
// Select all and delete to clear the default title
await pressKey(this.app, 'Control+a');
await new Promise(resolve => setTimeout(resolve, 100));
await pressKey(this.app, 'Delete');
await new Promise(resolve => setTimeout(resolve, 100));
// Type the tiddler title
await typeText(this.app, "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor", tiddlerTitle);
await new Promise(resolve => setTimeout(resolve, 500));
// Add the custom field
await clickElement(this.app, "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-name-wrapper input");
await new Promise(resolve => setTimeout(resolve, 200));
await typeText(this.app, "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-name-wrapper input", fieldName);
await new Promise(resolve => setTimeout(resolve, 200));
await clickElement(this.app, "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-value input");
await new Promise(resolve => setTimeout(resolve, 200));
await typeText(this.app, "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-value input", fieldValue);
await new Promise(resolve => setTimeout(resolve, 200));
await clickElement(this.app, "div[data-tiddler-title^='Draft of'] .tc-edit-field-add button");
await new Promise(resolve => setTimeout(resolve, 300));
// Click confirm button to save
await clickElement(this.app, 'button:has(.tc-image-done-button)');
await new Promise(resolve => setTimeout(resolve, 500));
});

View file

@ -784,7 +784,7 @@ ${tiddler.content}
await fs.ensureDir(settingsDirectory);
let settings: { workspaces?: Record<string, IWorkspace> } & Record<string, unknown> = {};
if (await fs.pathExists(settingsPath)) {
settings = await fs.readJson(settingsPath);
settings = await fs.readJson(settingsPath) as { workspaces?: Record<string, IWorkspace> };
}
// Generate unique IDs

View file

@ -20,56 +20,23 @@ Feature: Sub-Wiki Functionality
When I click on a "default wiki workspace button" element with selector "div[data-testid^='workspace-']:has-text('wiki')"
Then the browser view should be loaded and visible
And I wait for SSE and watch-fs to be ready
# Create tiddler with tag to test file system plugin
And I click on "add tiddler button" element in browser view with selector "button:has(.tc-image-new-button)"
# Focus on title input, clear it, and type new title in the draft tiddler
And I click on "title input" element in browser view with selector "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor"
And I wait for 0.2 seconds
And I press "Control+a" in browser view
And I wait for 0.2 seconds
And I press "Delete" in browser view
And I type "TestTiddlerTitle" in "title input" element in browser view with selector "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor"
# Wait for tiddler state to settle, otherwise it still shows 3 chars (新条目) for a while
And I wait for 2 seconds
Then I should see "16 chars" in the browser view content
# Input tag by typing in the tag input field - use precise selector to target the tag input specifically
And I click on "tag input" element in browser view with selector "div[data-tiddler-title^='Draft of'] div.tc-edit-add-tag-ui input.tc-edit-texteditor[placeholder='']"
And I wait for 0.2 seconds
And I press "Control+a" in browser view
And I wait for 0.2 seconds
And I press "Delete" in browser view
And I wait for 0.2 seconds
And I type "TestTag" in "tag input" element in browser view with selector "div[data-tiddler-title^='Draft of'] div.tc-edit-add-tag-ui input.tc-edit-texteditor[placeholder='']"
# Click the add tag button to confirm the tag (not just typing)
And I wait for 0.2 seconds
And I click on "add tag button" element in browser view with selector "div[data-tiddler-title^='Draft of'] span.tc-add-tag-button button"
# Wait for file system plugin to save the draft tiddler to SubWiki folder, Even 3 second will randomly failed in next step.
And I wait for 4.5 seconds
# Verify the DRAFT tiddler has been routed to sub-wiki immediately after adding the tag
Then file "Draft of ''.tid" should exist in "{tmpDir}/SubWiki"
# Verify the draft file is NOT in main wiki tiddlers folder (it should have been moved to SubWiki)
Then file "Draft of ''.tid" should not exist in "{tmpDir}/wiki/tiddlers"
# Click confirm button to save the tiddler
And I click on "confirm button" element in browser view with selector "button:has(.tc-image-done-button)"
And I wait for 1 seconds
# Verify the final tiddler file exists in sub-wiki folder after save
# After confirming the draft, it should be saved as TestTiddlerTitle.tid in SubWiki
# Create tiddler with tag to test routing to sub-wiki folder
When I create a tiddler "TestTiddlerTitle" with tag "TestTag" in browser view
And I wait for 3 seconds for "tiddler to be saved and routed to sub-wiki"
# Verify the tiddler file exists in sub-wiki folder after save
Then file "TestTiddlerTitle.tid" should exist in "{tmpDir}/SubWiki"
# Test SSE is still working after SubWiki creation - modify a main wiki tiddler
# Verify tiddler is NOT in main wiki tiddlers folder
Then file "TestTiddlerTitle.tid" should not exist in "{tmpDir}/wiki/tiddlers"
# Test SSE is still working - modify a main wiki tiddler
When I modify file "{tmpDir}/wiki/tiddlers/Index.tid" to contain "Main wiki content modified after SubWiki creation"
Then I wait for tiddler "Index" to be updated by watch-fs
# Confirm Index always open
Then I should see a "Index tiddler" element in browser view with selector "div[data-tiddler-title='Index']"
Then I should see "Main wiki content modified after SubWiki creation" in the browser view content
# Test modification in sub-wiki folder - tiddler was routed there by tag
# Modify the tiddler file externally - need to preserve .tid format with metadata
# Test modification in sub-wiki folder
When I modify file "{tmpDir}/SubWiki/TestTiddlerTitle.tid" to contain "Content modified in SubWiki folder"
# Wait for watch-fs to detect the change - use longer wait and open tiddler directly
And I wait for 2 seconds for "watch-fs to detect file change in sub-wiki"
# Open the tiddler directly to verify content was updated
When I open tiddler "TestTiddlerTitle" in browser view
And I wait for 1 seconds
# Verify the modified content appears in the wiki
Then I should see "Content modified in SubWiki folder" in the browser view content
@subwiki @subwiki-load
@ -123,39 +90,20 @@ Feature: Sub-Wiki Functionality
When I open tiddler "TiddlerB" in browser view
And I wait for 0.5 seconds
Then I should see "TiddlerB with TiddlerA tag" in the browser view content
# Now create TiddlerC with tag TiddlerB (testing tag tree routing: TiddlerC -> TiddlerB -> TiddlerA -> TagTreeRoot)
And I click on "add tiddler button" element in browser view with selector "button:has(.tc-image-new-button)"
And I click on "title input" element in browser view with selector "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor"
And I wait for 0.2 seconds
And I press "Control+a" in browser view
And I wait for 0.2 seconds
And I press "Delete" in browser view
And I type "TiddlerC" in "title input" element in browser view with selector "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor"
And I wait for 0.5 seconds
# Add TiddlerB as a tag (testing tag tree traversal: TiddlerC -> TiddlerB -> TiddlerA -> TagTreeRoot)
And I click on "tag input" element in browser view with selector "div[data-tiddler-title^='Draft of'] div.tc-edit-add-tag-ui input.tc-edit-texteditor[placeholder='']"
And I wait for 0.2 seconds
And I type "TiddlerB" in "tag input" element in browser view with selector "div[data-tiddler-title^='Draft of'] div.tc-edit-add-tag-ui input.tc-edit-texteditor[placeholder='']"
And I wait for 0.2 seconds
And I click on "add tag button" element in browser view with selector "div[data-tiddler-title^='Draft of'] span.tc-add-tag-button button"
And I wait for 0.5 seconds
And I click on "confirm button" element in browser view with selector "button:has(.tc-image-done-button)"
# Create TiddlerC with tag TiddlerB (testing tag tree routing: TiddlerC -> TiddlerB -> TiddlerA -> TagTreeRoot)
When I create a tiddler "TiddlerC" with tag "TiddlerB" in browser view
And I wait for 3 seconds for "TiddlerC to be saved via tag tree routing"
# Verify TiddlerC is saved to sub-wiki via tag tree (TiddlerB -> TiddlerA -> TagTreeRoot)
# This confirms in-tagtree-of filter is working correctly
Then file "TiddlerC.tid" should exist in "{tmpDir}/SubWikiTagTree"
# Verify that TiddlerC is NOT in main wiki tiddlers folder
Then file "TiddlerC.tid" should not exist in "{tmpDir}/wiki/tiddlers"
@subwiki @subwiki-filter
Scenario: Tiddlers matching custom filter are routed to sub-wiki
# Setup: Create sub-wiki with custom filter that matches tiddlers with "FilterTest" field
# The filter "[has[filtertest]]" will match any tiddler with a "filtertest" field
# Setup: Create sub-wiki with custom filter that matches tiddlers with "filtertest" field
Given I cleanup test wiki so it could create a new one on start
And I setup a sub-wiki "SubWikiFilter" with tag "FilterTag" and filter "[has[filtertest]]" and tiddlers:
| title | tags | content |
| FilterTiddlerA | FilterTag | TiddlerA matched by filter |
# Now launch the app
When I launch the TidGi application
And I wait for the page to load completely
Then I should see "page body and workspaces" elements with selectors:
@ -165,31 +113,10 @@ Feature: Sub-Wiki Functionality
Then the browser view should be loaded and visible
And I wait for SSE and watch-fs to be ready
# Create a tiddler with the "filtertest" field to test filter routing
And I click on "add tiddler button" element in browser view with selector "button:has(.tc-image-new-button)"
And I click on "title input" element in browser view with selector "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor"
And I wait for 0.2 seconds
And I press "Control+a" in browser view
And I wait for 0.2 seconds
And I press "Delete" in browser view
And I type "FilterMatchTiddler" in "title input" element in browser view with selector "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor"
And I wait for 0.5 seconds
# Add the "filtertest" field by clicking on add field button
And I click on "add field name input" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-name-wrapper input"
And I wait for 0.2 seconds
And I type "filtertest" in "add field name input" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-name-wrapper input"
And I wait for 0.2 seconds
And I click on "add field value input" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-value input"
And I wait for 0.2 seconds
And I type "yes" in "add field value input" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-value input"
And I wait for 0.2 seconds
And I click on "add field button" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add button"
And I wait for 0.5 seconds
# Confirm to save the tiddler
And I click on "confirm button" element in browser view with selector "button:has(.tc-image-done-button)"
When I create a tiddler "FilterMatchTiddler" with field "filtertest" set to "yes" in browser view
And I wait for 3 seconds for "FilterMatchTiddler to be saved via filter routing"
# Verify FilterMatchTiddler is saved to sub-wiki via filter
Then file "FilterMatchTiddler.tid" should exist in "{tmpDir}/SubWikiFilter"
# Verify that FilterMatchTiddler is NOT in main wiki tiddlers folder
Then file "FilterMatchTiddler.tid" should not exist in "{tmpDir}/wiki/tiddlers"
@subwiki @subwiki-settings-ui

42
features/tiddler.feature Normal file
View file

@ -0,0 +1,42 @@
Feature: Tiddler Creation and Editing
As a user
I want to create and edit tiddlers in the wiki
So that I can manage my content
Background:
Given I cleanup test wiki so it could create a new one on start
And I launch the TidGi application
And I wait for the page to load completely
Then I should see a "default wiki workspace" element with selector "div[data-testid^='workspace-']:has-text('wiki')"
When I click on a "default wiki workspace button" element with selector "div[data-testid^='workspace-']:has-text('wiki')"
Then the browser view should be loaded and visible
And I wait for SSE and watch-fs to be ready
@tiddler @tiddler-create
Scenario: Create a new tiddler with tag and custom field via UI
# These are micro steps of `When I create a tiddler "MyTestTiddler" with field "customfield" set to "customvalue" in browser view` and `When I create a tiddler "MyTestTiddler" with tag "MyTestTag" in browser view`
# Click add tiddler button
And I click on "add tiddler button" element in browser view with selector "button:has(.tc-image-new-button)"
# Focus on title input, clear it, and type new title
And I click on "title input" element in browser view with selector "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor"
And I press "Control+a" in browser view
And I press "Delete" in browser view
And I type "MyTestTiddler" in "title input" element in browser view with selector "div[data-tiddler-title^='Draft of'] input.tc-titlebar.tc-edit-texteditor"
# Add a tag
And I click on "tag input" element in browser view with selector "div[data-tiddler-title^='Draft of'] div.tc-edit-add-tag-ui input.tc-edit-texteditor[placeholder='']"
And I type "MyTestTag" in "tag input" element in browser view with selector "div[data-tiddler-title^='Draft of'] div.tc-edit-add-tag-ui input.tc-edit-texteditor[placeholder='']"
And I click on "add tag button" element in browser view with selector "div[data-tiddler-title^='Draft of'] span.tc-add-tag-button button"
# Add a custom field
And I click on "add field name input" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-name-wrapper input"
And I type "customfield" in "add field name input" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-name-wrapper input"
And I click on "add field value input" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-value input"
And I type "customvalue" in "add field value input" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add-value input"
And I click on "add field button" element in browser view with selector "div[data-tiddler-title^='Draft of'] .tc-edit-field-add button"
# Confirm to save the tiddler
And I click on "confirm button" element in browser view with selector "button:has(.tc-image-done-button)"
# Verify the tiddler was created and is visible
Then I should see a "MyTestTiddler tiddler" element in browser view with selector "div[data-tiddler-title='MyTestTiddler']"
# Verify the tag was added
Then I should see a "MyTestTag tag" element in browser view with selector "[data-tiddler-title='MyTestTiddler'] [data-tag-title='MyTestTag']"
# Verify the tiddler file was created
Then file "MyTestTiddler.tid" should exist in "{tmpDir}/wiki/tiddlers"