Konzepte

Die dreischichtige Architektur, Naming-Konventionen und wie die Teile zusammenpassen.

← Zurück zur Übersicht · 🇬🇧 English · ← Quickstart · API-Referenz →


Die drei Schichten

CC-Testframework organisiert E2E-Test-Code in drei strikte Schichten, gescaffoldet innerhalb eines einzigen pm/-Projektordners:

pm/
├── 2_Apps/             ← Deine app-spezifischen Helfer
│   ├── 1_Global/       ← App-übergreifende Konfiguration + Master-References-Barrel
│   └── <DeineApp>/     ← Ein Ordner pro zu testender Anwendung
│       ├── 1_Controls/ ← Low-Level-UI-Element-Wrapper
│       ├── 2_Steps/    ← Business-Level-Test-Steps
│       └── 3_Blocks/   ← Komposite Step-Sequenzen (optional)
└── 3_Cases/            ← Deine eigentlichen Test-Cases — TC_*.spec.ts Dateien

💡 Wo ist Core? Framework-Code (Action, Check, Step, SearchEngine, …) kommt als npm-Paket @meintest/cc-testframework. Da pm/ ein eigenständiges npm-Projekt ist (eigene package.json, installiert mit cd pm && npm install), landet Core unter pm/node_modules/@meintest/cc-testframework/ — innerhalb von pm/, nicht im äußeren Repo-Root. Du editierst ihn nie — siehe Schicht 1 unten.

Jede Schicht hat einen klaren Vertrag darüber, was reingehört und was nicht. Test-Autoren, die einen TestCase (Schicht 3) schreiben, rufen ausschließlich TestSteps (Schicht 2) auf, die ihrerseits Controls (Schicht 2) und Core (Schicht 1) aufrufen. Diese Disziplin erzeugt Tests, die sich lesen wie Geschäftsanforderungs-Spezifikationen — nicht wie Playwright-Code.


Die komplette Ordnerstruktur

pm/ wird von der ersten Sekunde an mit allen 13 nummerierten Ordnern gescaffoldet — die meisten Projekte nutzen im Alltag nur eine Handvoll davon aktiv, aber die Slots existieren, damit jedes Tooling, das sie liest oder beschreibt, eine kanonische Anlaufstelle hat.

Ordner Zweck
pm/2_Apps/ Deine app-spezifischen Helfer — Controls, TestSteps, Blocks (Schicht 2, unten)
pm/3_Cases/ Deine TestCases — TC_*.spec.ts (Schicht 3, unten)
pm/4_Sets/ Test-Set-Gruppierungen — welche TestCases für einen Lauf zusammengehören
pm/5_Plans/ Testpläne
pm/6_Requirements/ Anforderungen / Traceability-Records
pm/7_Assets/ Referenz-Screenshots für Self-Healing, geschlüsselt via <uid>/<refId>.png — siehe Self-Healing Locators
pm/8_Defects/ Defekt-Records
pm/9_Scheduler/ Geplante/automatisierte Lauf-Definitionen
pm/10_Downloads/ Standard-Download-Ziel (Core.Constant.downloadDir)
pm/11_TestData/ Testdaten-Fixtures (Core.Constant.testDataDir)
pm/12_Logging/ Log-Ausgabe (Core.Constant.logDir)
pm/13_RemoteAgents/ Remote-Automatisierungs-Agent-Dateien — committete Task-Request-Dateien
pm/14_Results/ Dauerhafte, committete Lauf-Historien-Records (eine Datei pro Ausführung)

pm/ trägt als eigenständiges npm-Projekt auch seine eigenen Config-Dateien — Geschwister der 13 nummerierten Ordner, nicht einer von ihnen:

Datei Zweck
pm/package.json Das Projekt-Manifest — @meintest/cc-testframework, @playwright/test und die Laufzeit-Abhängigkeiten deiner mitgelieferten Apps
pm/tsconfig.json TypeScript-Config — baseUrl: './' und die @Apps/*/@Cases/*/@TestData/*-Path-Aliases, alle relativ zu pm/ selbst
pm/playwright.config.ts Playwright-Config — testDir: './3_Cases', deine baseURL, Projects
pm/.npmrc GitHub-Packages-Registry + Auth-Token-Referenz, damit npm install innerhalb von pm/ @meintest/...-Pakete auflösen kann
pm/.cc-scaffold.json Baseline-Marker, geschrieben von init/add-app — der ursprüngliche SHA-256-Hash jeder gescaffoldeten Control-/Step-Datei. Ermöglicht cc-testframework-update-app, Template-Drift ohne Raten zu erkennen. Committe sie.

Siehe Quickstart — Schritt 3 dafür, wie diese gescaffoldet werden, und Migration auf v0.25.0 unten, falls du ein bestehendes Projekt aktualisierst.


Migration auf v0.24.0 (Projekt-Layout)

⚠ Breaking Change, kein Backward-Compat-Shim — das Framework befindet sich weiterhin in aktiver Entwicklung. Hast du ein Projekt vor v0.24.0 gescaffoldet, führe diese Verschiebungen von Hand durch.

Vorher (< v0.24.0) Nachher (≥ v0.24.0)
tests/ (Projekt-Root) pm/
4_Download/ pm/10_Downloads/
5_TestData/ pm/11_TestData/
6_Logging/ pm/12_Logging/
3_Cases/Assets/<TestCase-Titel>/<refId>.png pm/7_Assets/<uid>/<refId>.png (erfordert Core.setTestCaseId('tc_<slug>') — siehe Self-Healing Locators — Einem TestCase eine stabile ID geben)

Zum Zeitpunkt dieser Migration lagen playwright.config.ts und package.json noch im äußeren Repo-Root, mit testDir: './pm' und projectDir = './pm' nach innen zeigend — siehe Migration auf v0.25.0 unten dafür, wo diese Dateien jetzt liegen.


Migration auf v0.25.0 (autonomes pm/-Projekt)

⚠ Breaking Change, kein Backward-Compat-Shim — das Framework befindet sich weiterhin in aktiver Entwicklung.

v0.25.0 verschiebt package.json, tsconfig.json, playwright.config.ts und .npmrc aus dem äußeren Repo-Root in pm/ selbst — pm/ wird zu einem eigenständigen npm-Projekt, und dein äußerer Repo-Root bekommt keine dieser Dateien mehr:

Vorher (< v0.25.0) Nachher (≥ v0.25.0)
package.json, node_modules/, .npmrc (äußerer Root) pm/package.json, pm/node_modules/, pm/.npmrc
tsconfig.json (äußerer Root) pm/tsconfig.json (Path-Aliases verlieren das pm/-Präfix — @Apps/*2_Apps/*, und so weiter)
playwright.config.ts (äußerer Root, testDir: './pm') pm/playwright.config.ts (testDir: './3_Cases')
npm install (äußerer Root) cd pm && npm install
npx playwright test pm/3_Cases/TC_*.spec.ts (äußerer Root) npx playwright test 3_Cases/TC_*.spec.ts (aus pm/ heraus)

Das ändert auch, wo die eigene Repo-Root-Erkennung des Frameworks landet: sie löst jetzt auf pm/ auf (den Ordner, der playwright.config.ts und package.json trägt), nicht den äußeren Ordner, der pm/ enthält. Alles, was das Framework relativ zu dieser Root schreibt — .self-healing-report.json, .cc-testframework.local.json, den Playwright-HTML-Report, die Referenz-Screenshot-Auflösung — landet dadurch jetzt innerhalb von pm/. Siehe Self-Healing Locators — Migration auf v0.25.0 dafür, was das konkret für Referenz-Asset-Pfade bedeutet.

Um ein bestehendes Projekt zu migrieren: verschiebe die @meintest/cc-testframework-/@playwright/test-/App-Abhängigkeiten aus deiner äußeren package.json in eine neue pm/package.json, verschiebe tsconfig.json/playwright.config.ts/.npmrc mit den obigen Pfad-Anpassungen nach pm/, lösche die äußeren Kopien, und führe dann cd pm && npm install aus.


Schicht 1: Core (das Framework)

Liegt nach cd pm && npm install unter pm/node_modules/@meintest/cc-testframework/dist/. Du editierst hier nichts — das ist das Framework.

Was drin ist:

  • Action — Clicks, Fills, Hovers, Scrolls, Browser-Navigation
  • Check — Assertions auf Labels, URLs, Element-Existenz, Dialog-Zustände
  • Step — Block-Strukturen, nummerierte Steps, Parameter-Logging
  • SearchEngine — Locator-Strategien (per Name, per XPath, per Hierarchie)
  • AppReady — Page/Browser-Lifecycle-Helfer
  • Filesystem, Logger, PasswordManager, Utility-Klassen
  • Re-Exports von Playwrights test, expect, plus nützliche Node-stdlib-Teile (fs, path, etc.)
  • baseConfig — ein Playwright-Konfigurations-Objekt, das du in deine eigene playwright.config.ts spreadest

Der Vertrag: Core-Exports sind stabil über Patch- und Minor-Versionen. Breaking Changes passieren nur bei Major-Bumps mit klarem Changelog-Eintrag. Darauf kannst du dich verlassen.


Schicht 2: Apps (deine Verkabelung)

Hier schreibst du app-spezifische Helfer. Die Struktur hat drei Unter-Schichten:

1_Controls/ — Low-Level-Element-Wrapper

Ein Control kapselt die Locator-Strategie für ein einzelnes logisches UI-Element oder eine Komponente. Beispiel:

// 2_Apps/MyApp/1_Controls/Control_Button.ts
import * as Core from '@meintest/cc-testframework';

export async function findButton(page: Core.Page, viewName: string, viewType: string, label: string) {
    return Core.SearchEngine.findLocators(page, {
        view: { name: viewName, type: viewType },
        element: { tag: 'button', text: label },
    });
}

💡 Warum eine separate Control-Schicht? Wenn deine Anwendung den Button-Selector ändert (z.B. von <button> auf <a role="button">), aktualisierst du eine Control-Datei — alle TestSteps, die diesen Control nutzen, profitieren automatisch. Ohne diese Schicht müsste jeder TestStep UI-Implementations-Details kennen, und ein kleines UI-Refactor würde durch Dutzende von Test-Dateien kaskadieren.

Locators, die mit Core.xpath getaggt sind (statt eines rohen Template-Literal-Strings), werden Self-Healing-fähig: schlägt ein getaggter Locator zur Laufzeit fehl und ist für diesen TestStep ein Referenz-Screenshot gebunden, kann das Framework automatisch einen Ersatz finden und in diese Control-Datei zurückschreiben. Das ist optional und rein additiv — siehe Self-Healing Locators für das vollständige Setup.

2_Steps/ — Business-Level-Aktionen

Ein TestStep kombiniert Controls zu einer einzelnen geschäftlich bedeutsamen Aktion. Naming: TS_<Region>_<ElementType>_<Verb>.

// 2_Apps/MyApp/2_Steps/Step_Button.ts
import * as Core from '@meintest/cc-testframework';
import * as Control from '@MyAppControls/Control_Button';

export async function TS_Main_Button_Click(
    viewName: string,
    viewType: string,
    label: string,
) {
    const locator = await Control.findButton(Core.page, viewName, viewType, label);
    await Core.Action.click(locator, `${viewName}${label}`);
}

Ein TestStep liest sich wie eine Anweisung an einen menschlichen Tester: “auf der Main-View klicke den Button mit Label X”. Der TestCase-Autor interessiert sich nicht wie der Button gefunden wird — das ist Controls Job.

3_Blocks/ — Komposite Step-Sequenzen (optional)

Ein Block ist eine wiederverwendbare Sequenz von TestSteps für höhere Szenarien. Nützlich bei repetitiven mehrstufigen Setups über viele TestCases (z.B. “als Testuser einloggen”, “ein Beispiel-Dokument anlegen”). Naming: TSB_<Szenario>.

// 2_Apps/MyApp/3_Blocks/TSB_Login.ts
import * as Steps from '@MyAppSteps';

export async function TSB_Login_AsTestUser() {
    await Steps.TS_Main_Textfield_Fill('Login', '', 'Username', 'testuser');
    await Steps.TS_Main_Textfield_Fill('Login', '', 'Password', 'testpassword');
    await Steps.TS_Main_Button_Click('Login', '', 'Sign in');
}

Wenn du keine repetitiven Sequenzen hast, lass Blocks weg — sie sind optional.

1_Global/ — App-übergreifende Konfiguration

Enthält:

  • References.ts — die Barrel-Datei, die alle App-Exports für TestCases aggregiert
  • GlobalConfig.ts — Umgebungs-URLs, Timeouts, etc.
  • GlobalSetup.ts — Playwright Global-Setup-Hook

Schicht 3: Cases (deine Tests)

Ein TestCase ist ein Playwright-test()-Block, der TestSteps (und optional Blocks) zu einem Business-Szenario komponiert. Naming: TC_<Szenario>.spec.ts.

// 3_Cases/TC_UserCreation.spec.ts
import * as Project from '@GlobalRef';

Project.Core.test('TC_UserCreation', async ({ page }) => {
    Project.Core.Step.setCurrentTestCaseName('TC_UserCreation');

    await Project.Core.Step.numberedStepBlock('Login', async () => {
        await Project.MyApp.TSB_Login_AsTestUser();
    });

    await Project.Core.Step.numberedStepBlock('Open user creation dialog', async () => {
        await Project.MyApp.TS_Main_Menu_ClickItem('Dashboard', '', 'Users');
        await Project.MyApp.TS_Main_Button_Click('Users', '', 'Add User');
    });

    await Project.Core.Step.numberedStepBlock('Fill user form', async () => {
        await Project.MyApp.TS_Dialog_Textfield_Fill('Add User', '', 'Email', 'jane@example.com');
        await Project.MyApp.TS_Dialog_Button_Click('Add User', '', 'Create');
    });

    await Project.Core.Step.numberedStepBlock('Verify success message', async () => {
        await Project.MyApp.TS_Dialog_Label_ByXpath_CheckIsEqual(
            'Confirmation',
            '',
            './/p',
            'User created.',
        );
    });
});

TestCases enthalten keine Low-Level-Locators oder rohe Playwright-Calls. Alles geht durch TestSteps. Diese Disziplin macht das Framework wertvoll.


Naming-Konventionen auf einen Blick

Schicht Präfix Beispiel Liest sich als
Control (Dateiname) Control_* Control_Button.ts “Wrapper für Button-Elemente”
TestStep TS_<Region>_<ElementType>_<Verb> TS_Main_Button_Click “in der Main-Region einen Button klicken”
Step Block TSB_<Szenario> TSB_Login_AsTestUser “der Block fürs Einloggen als Testuser”
TestCase TC_<Szenario> TC_UserCreation.spec.ts “der TestCase für User-Erstellung”

Die Präfixe sind nicht dekorativ — sie sagen anderen Team-Mitgliedern auf einen Blick, zu welcher Schicht eine Datei gehört und welche Art Code drin zu erwarten ist.

Diese Disziplin hält Refactoring lokal (eine UI-Änderung berührt einen Control, eine Business-Logik-Änderung berührt einen TestStep) und hält TestCases lesbar als Business-Spezifikation statt als Playwright-Code. Ein neues Team-Mitglied kann TestCases schreiben, sobald es nur die TS_-Prefix-Konvention kennt — Playwright-Locator-Semantik ist dafür noch nicht nötig.


Wo es weitergeht

  • Neue App hinzufügen — deine zu testende Anwendung registrieren und das passende Plattform-Tool wählen
  • API-Referenz — was Core (Action, Check, Step, …) für TestSteps und Controls anbietet
  • Self-Healing Locators — automatische Locator-Reparatur über Referenz-Screenshots
  • FAQ — Antworten auf “brauche ich wirklich Blocks?”, “kann ich page.locator() direkt nutzen?”, “was wenn meine App React/Vue/Angular-spezifisch ist?”

📧 Fragen? Kontakt: jens.szelag@itsbusiness.ch

itsbusiness AG · Bern · Schweiz