opensource-joe opened a new issue, #3466:
URL: https://github.com/apache/apisix-dashboard/issues/3466

   ### Issue description
   
   `e2e/tests/plugin_metadata.crud-all-fields.spec.ts:88` ("should CRUD plugin 
metadata with all fields") fails locally somewhere between a third and two 
thirds of the time on current `master`. It is a test-suite race, not a product 
bug, and it is invisible in CI because `playwright.config.ts` sets `retries: 
process.env.CI ? 2 : 0`.
   
   The failure is always in the final step, `verify configuration changes were 
saved`:
   
   ```
   Expected pattern: /"time"\s*:\s*"\$time_iso8601"/
   Received string:  "{"
         205 |     // Get Monaco editor value using helper
         206 |     const editorValue = await 
getMonacoEditorValue(editPluginDialog);
       > 207 |     expect(editorValue).toMatch(/"time"\s*:\s*"\$time_iso8601"/);
   ```
   
   The received value is a single `{`, so the assertion is running against a 
Monaco editor that has mounted but not yet painted its content.
   
   `getMonacoEditorValue` (defined at the top of the same spec, line 36) 
already anticipates this and has two escape hatches, but both can observe the 
same half-rendered instant:
   
   ```ts
   await textarea.waitFor({ state: 'attached', timeout: 10000 });
   // ...
   if (await textarea.count() > 0) {
     editorValue = await textarea.inputValue();
   }
   // Fallback to reading view-lines if textarea value is incomplete
   if (!editorValue || editorValue.trim() === '{') {
     await editPluginDialog.locator('.view-line').first().waitFor({ timeout: 
10000 });
     const lines = await 
editPluginDialog.locator('.view-line').allTextContents();
     editorValue = lines.join('\n').replace(/\s+/g, ' ');
   }
   ```
   
   `waitFor({ state: 'attached' })` resolves as soon as the textarea exists, 
which is before Monaco has a model. The `.view-line` fallback waits only for 
the *first* line to exist, and the first line of the JSON is `{`, so on a slow 
paint the fallback is satisfied by exactly the state it was written to escape. 
Each check is a single observation, so there is no retry once both have been 
taken.
   
   ### Expected behavior
   
   The step should read the editor's settled content and assert against it, 
passing deterministically on a correctly saved config.
   
   ### How to Reproduce
   
   ```
   cd e2e/server && docker compose up -d --build
   pnpm dev
   E2E_TARGET_URL=http://localhost:5173/ui/ npx playwright test --workers=1 
--repeat-each=6 \
     e2e/tests/plugin_metadata.crud-all-fields.spec.ts
   ```
   
   `--repeat-each` is the important part: a single run passes often enough to 
look fine. Measured on `master` at `045e3142`, and on a branch off it, running 
against a real APISIX from `e2e/server`:
   
   | | failed | passed |
   |---|---|---|
   | `master`, 6 runs | 4 | 2 |
   | a branch off it, 9 runs | 4 | 5 |
   
   Both sides are the same test-suite race; the spread is sample noise, not a 
difference between the two trees.
   
   ### Environment
   
   - apisix-dashboard version: `master` @ `045e3142`
   - Browser: Chromium (Playwright `chromium-headless-shell` 145.0.7632.6, 
`@playwright/test` 1.58.2)
   - Gateway: `e2e/server` compose (`apache/apisix:dev` + 
`bitnamilegacy/etcd:3.5`)
   - OS: Linux container on macOS arm64
   
   ### Additional context
   
   Found while verifying #3465, where it showed up as a single failure in a 
scoped e2e run. Worth saying explicitly that my first reading of it was wrong: 
one run per side said "passes on master, fails on the branch", which looked 
like a regression from that PR. Repeating it produced the table above and 
showed it is pre-existing. Flagging that because anyone who hits this once 
while reviewing a PR will reach the same wrong conclusion.
   
   I would suggest replacing the single-observation reads with a poll until the 
content is actually parseable, so the helper waits for a *settled* editor 
rather than a populated one:
   
   ```ts
   await expect
     .poll(async () => {
       const raw = await textarea.inputValue();
       try {
         return Object.keys(JSON.parse(raw)).length;
       } catch {
         return 0;
       }
     }, { timeout: 10_000 })
     .toBeGreaterThan(0);
   ```
   
   That makes the success condition "the editor holds valid JSON with content" 
instead of "the editor holds something", which is the property the assertions 
afterwards actually depend on, and it removes the need for the `.view-line` 
fallback entirely.
   
   This relates to the test-suite gaps already listed in #3417 (15 
`waitForTimeout` sites, parallel-unsafe locally). This one is arguably worse 
than those, because CI retries hide it completely, so the suite reads as green 
while the race is real.
   
   Happy to send a PR for the helper change if that is useful.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to