Wer kennt es nicht? Die Cypress-Tests laufen lokal einwandfrei, aber auf Testing oder Live knallt es. Der Übeltäter: Unterschiedliche CSS-Selektoren zwischen den Umgebungen. Ein Login-Button heißt mal #user
, mal .tx-felogin-input-username
. Wir zeigen euch eine Best Practice, die dieses Problem elegant löst und eure Tests deutlich wartbarer macht.
Das Problem mit Environment-spezifischen Selektoren
In der Realität sieht die HTML-Struktur auf verschiedenen Umgebungen oft unterschiedlich aus. Vielleicht nutzt ihr auf Testing noch ein älteres CMS-Plugin, während Live schon die neue Version hat. Oder die Preview-Umgebung hat andere CSS-Klassen als Production.
Hardcodierte Selektoren in euren Page Objects bedeuten: Eure Tests brechen, sobald ihr die Umgebung wechselt. Das frustriert nicht nur, es kostet auch wertvolle Entwicklungszeit.
Die Lösung: Ein zentrales Selektor-Management
Wir haben bei Never Code Alone ein System entwickelt, das automatisch die richtigen CSS-Selektoren basierend auf der aktuellen Umgebung auswählt. Das Prinzip: Zentrale Verwaltung statt verteiltes Chaos.
Schritt 1: Zentrale Selektor-Konfiguration
Erstellt eine zentrale Datei für alle eure Selektoren:
// cypress/support/selectors.ts
export const defaultSelectors = {
login: {
username: '#user',
password: '#pass',
submit: '#kc-login'
},
navigation: {
menu: '.main-menu',
search: '#search-box'
}
};
export const selectorOverrides = {
testing1: {
login: {
username: '.tx-felogin-input-username'
// password und submit bleiben default
}
},
preview: {
navigation: {
menu: '.nav-primary'
}
}
};
Schritt 2: Intelligente Selektor-Funktion
Das Herzstück ist eine smarte Funktion, die Environment-spezifische Overrides prüft und auf Defaults zurückfällt:
export function getSelector(path: string): string {
const env = Cypress.env('configFile') || 'live';
// Pfad in Objekt-Navigation aufteilen (z.B. 'login.username')
const keys = path.split('.');
// Erst in Overrides suchen
let selector = getNestedValue(selectorOverrides[env], keys);
// Falls nicht gefunden, Default verwenden
if (!selector) {
selector = getNestedValue(defaultSelectors, keys);
}
return selector;
}
function getNestedValue(obj: any, keys: string[]): string | null {
return keys.reduce((current, key) => current?.[key], obj);
}
Schritt 3: Saubere Page Objects
Eure Page Objects bleiben sauber und müssen nichts über Environments wissen:
// cypress/support/pageObjects/LoginPage.ts
class LoginPage {
fillUsername(username: string) {
cy.get(getSelector('login.username'))
.clear()
.type(username);
}
fillPassword(password: string) {
cy.get(getSelector('login.password'))
.clear()
.type(password);
}
submit() {
cy.get(getSelector('login.submit')).click();
}
}
Schritt 4: Tests ausführen
Die Umgebung steuert ihr einfach über die Command Line:
# Verwendet default Selektoren (#user)
npx cypress open
# Verwendet testing1 Overrides (.tx-felogin-input-username)
npx cypress open --env configFile=testing1
# Verwendet preview Overrides
npx cypress open --env configFile=preview
Best Practices für nachhaltiges Selektor-Management
1. Semantische Struktur verwenden
Organisiert eure Selektoren nach Features, nicht nach Seiten:
{
authentication: { ... },
navigation: { ... },
userProfile: { ... }
}
2. Data-Attributes für Stabilität
Wenn ihr Einfluss auf die Entwicklung habt, nutzt data-cy Attribute:
<button data-cy="login-submit">Login</button>
3. Fallback-Strategien implementieren
export function getSelectorWithFallback(primary: string, fallback: string): string {
const primarySelector = getSelector(primary);
return cy.get(primarySelector).should('exist').then($el => {
return $el.length > 0 ? primarySelector : getSelector(fallback);
});
}
4. Dokumentation ist Pflicht
export const defaultSelectors = {
login: {
// Verwendet auf: Live, Staging
// CMS: TYPO3 v11
username: '#user',
// Fallback für ältere Versionen
// Siehe: selectorOverrides.testing1
usernameAlt: '.tx-felogin-input-username'
}
};
Die Vorteile auf einen Blick
✅ Keine Code-Duplikation: Selektoren werden nur einmal definiert
✅ Einfache Wartung: Alle Selektoren an einem Ort
✅ Flexibilität: Neue Environments ohne Test-Änderungen
✅ Übersichtlichkeit: Klar erkennbar, was pro Environment anders ist
✅ Minimaler Refactoring-Aufwand: Bestehende Tests brauchen nur kleine Anpassungen
Troubleshooting-Tipps
Problem: Selektor wird nicht gefunden
Lösung: Debug-Modus aktivieren
export function getSelector(path: string, debug = false): string {
if (debug) {
console.log(`Searching selector for: ${path} in env: ${env}`);
}
// ...
}
Problem: Performance-Probleme bei vielen Selektoren
Lösung: Caching implementieren
const selectorCache = new Map<string, string>();
Fazit: Investition, die sich lohnt
Die initiale Einrichtung kostet vielleicht eine Stunde, spart aber unzählige Stunden Debugging-Frust. Bei uns im Team sind die „Test läuft nur lokal“-Tickets praktisch verschwunden.
Das System hat sich besonders bewährt bei:
- Multi-Stage Deployments
- Legacy-System-Migrationen
- Teams mit verschiedenen Testing-Umgebungen
- Projekten mit häufigen Frontend-Änderungen
Nächste Schritte
Wollt ihr das System in eurem Projekt implementieren? Hier unsere Empfehlung:
- Analysiert eure Umgebungen und dokumentiert Selektor-Unterschiede
- Erstellt die zentrale selectors.ts mit Defaults
- Fügt Environment-spezifische Overrides nur wo nötig hinzu
- Refactored einen Test als Proof of Concept
- Rollt das System schrittweise aus
Kontakt und Support
Habt ihr Fragen zur Implementierung oder braucht Unterstützung bei euren Cypress-Tests? Bei Never Code Alone bieten wir Workshops und Consulting zu Test-Automatisierung an. Cypress, Playwright, oder andere Frameworks – wir helfen euch, eure Tests auf das nächste Level zu bringen.
Schreibt uns einfach oder kommt zu einem unserer Community-Events. Gemeinsam machen wir eure Tests stabiler und eure Entwicklung effizienter!
Dieser Artikel ist Teil unserer Best Practice Serie zu Software-Qualität und Test-Automatisierung. Bei Never Code Alone setzen wir uns für nachhaltige Entwicklung und stabile Tests ein. Folgt uns für mehr praktische Tipps aus dem Entwickler-Alltag!