# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: applications/bwl/suite.spec.ts >> Imports for bwl >> generate-fakes dialog opens with real application periods
- Location: src/framework/specs/imports.spec.ts:153:11

# Error details

```
Error: expect(locator).toBeVisible() failed

Locator: locator('.p-dialog-mask .p-dialog').first().locator('[data-testid="fakes-period-select"]')
Expected: visible
Timeout: 5000ms
Error: element(s) not found

Call log:
  - Expect "toBeVisible" with timeout 5000ms
  - waiting for locator('.p-dialog-mask .p-dialog').first().locator('[data-testid="fakes-period-select"]')

```

# Page snapshot

```yaml
- generic [ref=e1]:
  - generic [ref=e2]:
    - generic [ref=e3]:
      - generic [ref=e5]:
        - generic [ref=e8]:
          - link "MasterEV Home" [ref=e9] [cursor=pointer]:
            - /url: /admin/applications/bwl
            - img [ref=e12]
          - generic [ref=e17]:
            - link "BMT München" [ref=e18] [cursor=pointer]:
              - /url: /admin/applications/bwl
            - button "E2E" [ref=e20] [cursor=pointer]:
              - generic [ref=e24]:
                - generic [ref=e25]: 
                - generic [ref=e26]: E2E
                - generic [ref=e27]: 
              - generic [ref=e28]: 
        - menubar [ref=e29]:
          - menuitem "Bewerbungen" [ref=e30]:
            - link "Bewerbungen" [ref=e32] [cursor=pointer]:
              - /url: /admin/applications/bwl/list
              - generic [ref=e33]: Bewerbungen
              - img [ref=e34]
          - menuitem "Stage 1" [ref=e36]:
            - link "Stage 1" [ref=e38] [cursor=pointer]:
              - /url: /admin/applications/bwl/process/stage1
              - generic [ref=e39]: Stage 1
              - img [ref=e40]
          - menuitem "Stage 2" [ref=e42]:
            - link "Stage 2" [ref=e44] [cursor=pointer]:
              - /url: /admin/applications/bwl/process/stage2
              - generic [ref=e45]: Stage 2
              - img [ref=e46]
        - generic [ref=e49]:
          - combobox "Suche in BMT München" [ref=e51]
          - generic [ref=e55] [cursor=pointer]:
            - generic [ref=e56]: 
            - generic [ref=e59]: E2E ADMIN applications-bwl-suite-spec-ts
            - generic [ref=e60]: 
      - generic [ref=e63]:
        - generic [ref=e67]:
          - heading "Bewerbungsverwaltung" [level=1] [ref=e68]
          - paragraph [ref=e69]: Importe, Bewerbungszeiträume und historische Daten verwalten
        - generic [ref=e73]:
          - tablist [ref=e76]:
            - tab "Imports" [selected] [ref=e78] [cursor=pointer]
            - tab "Application Periods" [ref=e80] [cursor=pointer]
            - tab "Historic Data" [ref=e82] [cursor=pointer]
          - generic [ref=e86]:
            - generic [ref=e88]:
              - generic [ref=e91]:
                - generic [ref=e92]: 
                - textbox "Suchen" [ref=e93]
              - button " Testdaten generieren" [ref=e95] [cursor=pointer]:
                - generic [ref=e96]: 
                - generic [ref=e97]: Testdaten generieren
              - button " Importieren" [ref=e99] [cursor=pointer]:
                - generic [ref=e100]: 
                - generic [ref=e101]: Importieren
            - generic [ref=e102]:
              - generic [ref=e104]:
                - generic [ref=e106] [cursor=pointer]:
                  - text: Importdatum
                  - generic [ref=e107]: 
                - generic [ref=e109]: Datei
                - generic [ref=e111]: Bewerbungszeiträume
                - generic [ref=e113] [cursor=pointer]:
                  - text: Ergebnis
                  - generic [ref=e114]: 
                - generic [ref=e116] [cursor=pointer]:
                  - text: Benutzer:in
                  - generic [ref=e117]: 
              - generic [ref=e122]: Keine Ergebnisse gefunden
      - generic [ref=e125]:
        - link " Impressum" [ref=e128] [cursor=pointer]:
          - /url: /pages/sitenotice
          - generic [ref=e129]: 
          - generic [ref=e130]: Impressum
        - link " Datenschutz" [ref=e133] [cursor=pointer]:
          - /url: /pages/privacy
          - generic [ref=e134]: 
          - generic [ref=e135]: Datenschutz
        - generic [ref=e139] [cursor=pointer]: 
    - generic:
      - generic:
        - alertdialog
  - dialog "Testdatengenerierung" [ref=e141]:
    - generic [ref=e145]: Testdatengenerierung
    - generic [ref=e147]:
      - generic [ref=e148]:
        - paragraph [ref=e149]: Hiermit können Sie jederzeit Testdaten (sog. Fake-Daten) zum Testen des Prozesses generieren. Die Datensätze können entweder direkt importiert oder als CSV-Datei heruntergeladen werden.
        - paragraph [ref=e150]: Die Daten können nach dem Testen einfach wieder gelöscht werden, indem der entsprechende Import revidiert wird.
      - alert [ref=e151]:
        - generic [ref=e154]: Keine Länderdaten geladen. Die Länderliste muss vor der Testdatengenerierung eingespielt werden.
      - generic [ref=e155]:
        - button " Abbrechen" [active] [ref=e157] [cursor=pointer]:
          - generic [ref=e158]: 
          - generic [ref=e159]: Abbrechen
        - button " Direkt Importieren" [disabled] [ref=e161]:
          - generic [ref=e162]: 
          - generic [ref=e163]: Direkt Importieren
        - button " CSV herunterladen" [disabled] [ref=e165]:
          - generic [ref=e166]: 
          - generic [ref=e167]: CSV herunterladen
```

# Test source

```ts
  82  |       await expect(searchInput).toBeVisible({ timeout: 5_000 });
  83  |       await searchInput.fill('test');
  84  |       await adminPage.waitForTimeout(1_000);
  85  |       await searchInput.fill('');
  86  |     });
  87  | 
  88  |     // ============================================================
  89  |     // Convex-only: Manage-Section tabs + period auto-creation flow
  90  |     // ============================================================
  91  |     if (actions.isConvex) {
  92  |       test('manage section redirects /manage to /manage/imports', async ({
  93  |         adminPage,
  94  |       }) => {
  95  |         await adminPage.goto(`/admin/applications/${actions.process}/manage`);
  96  |         await expect(adminPage).toHaveURL(
  97  |           new RegExp(`/applications/${actions.process}/manage/imports`),
  98  |           { timeout: 10_000 }
  99  |         );
  100 |       });
  101 | 
  102 |       test('manage section has three tabs (imports/periods/historic)', async ({
  103 |         adminPage,
  104 |       }) => {
  105 |         await adminPage.goto(importsPath);
  106 |         await expect(adminPage.locator('masterev-tabs')).toBeVisible({
  107 |           timeout: 10_000,
  108 |         });
  109 |         await expect(adminPage.locator('masterev-tablist')).toBeVisible();
  110 |         const tabs = adminPage.locator('masterev-tab');
  111 |         await expect(tabs).toHaveCount(3);
  112 |       });
  113 | 
  114 |       test('periods tab loads', async ({ adminPage }) => {
  115 |         await adminPage.goto(
  116 |           `/admin/applications/${actions.process}/manage/periods`
  117 |         );
  118 |         await expect(adminPage).toHaveURL(
  119 |           new RegExp(`/applications/${actions.process}/manage/periods`),
  120 |           { timeout: 10_000 }
  121 |         );
  122 |         // Period table is rendered by `masterev-application-periods-tab`.
  123 |         await expect(
  124 |           adminPage.locator('masterev-application-periods-tab')
  125 |         ).toBeVisible({ timeout: 10_000 });
  126 |       });
  127 | 
  128 |       test('historic tab loads', async ({ adminPage }) => {
  129 |         await adminPage.goto(
  130 |           `/admin/applications/${actions.process}/manage/historic`
  131 |         );
  132 |         await expect(adminPage).toHaveURL(
  133 |           new RegExp(`/applications/${actions.process}/manage/historic`),
  134 |           { timeout: 10_000 }
  135 |         );
  136 |         await expect(
  137 |           adminPage.locator('masterev-application-historic-tab')
  138 |         ).toBeVisible({ timeout: 10_000 });
  139 |       });
  140 | 
  141 |       // ==========================================================
  142 |       // Generate-fakes dialog: real application periods, empty
  143 |       // states, CSV download flow.
  144 |       //
  145 |       // The prerequisites (>=1 applicationPeriod, >=1 country) are
  146 |       // seeded idempotently via `e2e:ensureFakesPrerequisites`. On
  147 |       // staging the BMT customer can land in a no-periods state after
  148 |       // a semester reset, which would otherwise make these tests
  149 |       // assert against the empty-state error instead of the select.
  150 |       // ==========================================================
  151 |       const fakesPeriodShort = 'E2EFAKES';
  152 | 
  153 |       test('generate-fakes dialog opens with real application periods', async ({
  154 |         adminPage,
  155 |       }) => {
  156 |         await convexRun('e2e:ensureFakesPrerequisites', {
  157 |           customer: actions.customer,
  158 |           process: actions.process,
  159 |           periodShort: fakesPeriodShort,
  160 |         });
  161 | 
  162 |         await adminPage.goto(importsPath);
  163 |         await expect(
  164 |           adminPage.locator('masterev-infinite-data-table')
  165 |         ).toBeVisible({ timeout: 10_000 });
  166 | 
  167 |         await adminPage
  168 |           .locator('[data-testid="generate-fakes-button"]')
  169 |           .click();
  170 | 
  171 |         const dialog = adminPage.locator(DIALOG).first();
  172 |         await expect(dialog).toBeVisible({ timeout: 5_000 });
  173 |         await expect(
  174 |           dialog.locator('masterev-data-imports-generate-fakes-dialog')
  175 |         ).toBeVisible({ timeout: 5_000 });
  176 | 
  177 |         // Period select (Convex path) must be visible — neither empty
  178 |         // state should be active in the BMT test customer.
  179 |         const periodSelect = dialog.locator(
  180 |           '[data-testid="fakes-period-select"]'
  181 |         );
> 182 |         await expect(periodSelect).toBeVisible({ timeout: 5_000 });
      |                                    ^ Error: expect(locator).toBeVisible() failed
  183 |         await expect(
  184 |           dialog.locator('[data-testid="fakes-no-periods-error"]')
  185 |         ).toHaveCount(0);
  186 |         await expect(
  187 |           dialog.locator('[data-testid="fakes-no-countries-error"]')
  188 |         ).toHaveCount(0);
  189 | 
  190 |         // The selected option carries the period-tag component (same
  191 |         // visual language as the header period switcher). The tag is
  192 |         // rendered inside the p-select's "selectedItem" slot.
  193 |         await expect(
  194 |           periodSelect.locator('masterev-application-period-tag').first()
  195 |         ).toBeVisible({ timeout: 5_000 });
  196 | 
  197 |         // Open the dropdown panel and assert at least one option is
  198 |         // rendered with a period tag. `appendTo="body"` puts the panel
  199 |         // outside the dialog, so locate it on `adminPage`.
  200 |         await periodSelect.locator('p-select').click();
  201 |         const panel = adminPage.locator('.p-select-overlay');
  202 |         await expect(panel).toBeVisible({ timeout: 5_000 });
  203 |         await expect(
  204 |           panel.locator('masterev-application-period-tag').first()
  205 |         ).toBeVisible({ timeout: 5_000 });
  206 |         // Close the panel before cancelling so PrimeNG cleans up the
  207 |         // overlay before we assert the dialog disappears.
  208 |         await adminPage.keyboard.press('Escape');
  209 | 
  210 |         // Both action buttons are reachable and enabled (countries +
  211 |         // periods present).
  212 |         const importBtn = dialog.locator('[data-testid="fakes-import-button"]');
  213 |         const downloadBtn = dialog.locator(
  214 |           '[data-testid="fakes-download-button"]'
  215 |         );
  216 |         await expect(importBtn).toBeEnabled();
  217 |         await expect(downloadBtn).toBeEnabled();
  218 | 
  219 |         await dialog.locator('[data-testid="fakes-cancel-button"]').click();
  220 |         await expect(dialog).not.toBeVisible({ timeout: 5_000 });
  221 |       });
  222 | 
  223 |       test('generate-fakes "Download CSV" delivers a CSV with rows for the selected period', async ({
  224 |         adminPage,
  225 |       }) => {
  226 |         await convexRun('e2e:ensureFakesPrerequisites', {
  227 |           customer: actions.customer,
  228 |           process: actions.process,
  229 |           periodShort: fakesPeriodShort,
  230 |         });
  231 | 
  232 |         await adminPage.goto(importsPath);
  233 |         await expect(
  234 |           adminPage.locator('masterev-infinite-data-table')
  235 |         ).toBeVisible({ timeout: 10_000 });
  236 | 
  237 |         await adminPage
  238 |           .locator('[data-testid="generate-fakes-button"]')
  239 |           .click();
  240 |         const dialog = adminPage.locator(DIALOG).first();
  241 |         await expect(dialog).toBeVisible({ timeout: 5_000 });
  242 |         await expect(
  243 |           dialog.locator('[data-testid="fakes-period-select"]')
  244 |         ).toBeVisible({ timeout: 5_000 });
  245 | 
  246 |         // Read the period code from the selected-item period tag. The
  247 |         // CSV contains this as a row value (bewerbungszeitraum column);
  248 |         // the dialog passes `period.short` straight through to the
  249 |         // generateFakes action.
  250 |         const selectedShort = (
  251 |           await dialog
  252 |             .locator(
  253 |               '[data-testid="fakes-period-select"] masterev-application-period-tag'
  254 |             )
  255 |             .first()
  256 |             .innerText()
  257 |         ).trim();
  258 |         expect(selectedShort.length).toBeGreaterThan(0);
  259 | 
  260 |         const downloadPromise = adminPage.waitForEvent('download', {
  261 |           timeout: 30_000,
  262 |         });
  263 |         await dialog.locator('[data-testid="fakes-download-button"]').click();
  264 |         const download = await downloadPromise;
  265 | 
  266 |         // The backend's `fakeCsvFilename` shape is
  267 |         // `CAMPUSonline_<customer>_<process>_fakes.csv` and the period
  268 |         // is encoded inside rows, not the filename. Assert the shape
  269 |         // here so a future filename change is caught explicitly.
  270 |         const filename = download.suggestedFilename();
  271 |         expect(filename).toMatch(
  272 |           new RegExp(`^CAMPUSonline_.*${actions.process}.*_fakes\\.csv$`, 'i')
  273 |         );
  274 | 
  275 |         // Read the CSV body and assert the selected period appears in
  276 |         // at least one data row. Streaming-read via the download stream
  277 |         // keeps the file under control across all customers.
  278 |         const stream = await download.createReadStream();
  279 |         const chunks: Buffer[] = [];
  280 |         await new Promise<void>((resolve, reject) => {
  281 |           stream.on('data', (c) => chunks.push(Buffer.from(c)));
  282 |           stream.on('end', () => resolve());
```